/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.system;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.management.InstanceNotFoundException;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanServer;
import javax.management.MBeanServerNotification;
import javax.management.MalformedObjectNameException;
import javax.management.Notification;
import javax.management.NotificationFilter;
import javax.management.NotificationFilterSupport;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import org.jboss.logging.Logger;
import org.jboss.mx.server.ServerConstants;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* An abstract base class that provides for declarative JMX notification
* subscription handling.
* <p>
* A JBoss service that is in addition a NotificationListener can
* subclass ListenerServiceMBeanSupport instead of ServiceMBeanSupport
* and specify at run-time, inline in the MBean descriptor using the
* SubscriptionList attribute, the set of MBeans/notifications the
* service wants to subscribe/receive.
* <p>
* Call subscribe(boolean dynamicSubscriptions) at anytime to register to
* those MBeans and for those notifications that match the specified criteria.
* Call unsubscribe() to unsubscribe for Notifications.
* <p>
* If true is passed to subscribe() the baseclass will monitor for
* registration events from the MBeanServer and automatically subscribe
* to new instances of MBeans that match the subscription criteria.
* Monitoring for unsubscribe events in not necessary, since the MBeanServer
* automatically removes subscriptions to unregistering MBeans.
* <p>
* An alternative subscribe(boolean dynamicSubscription, ObjectName listener)
* can be used to specify a different MBean as the receiver of the
* subscribed notifications. The specified MBean must be a NotificationListener.
* <p>
* To handle the incoming notifications override the handleNotification2()
* method. The usual handleNotification() method should not be overriden,
* since it is used to monitor the incoming notifications for registration
* events coming from the MBeanServer, before delegating to
* handleNotification2(), in order to implement dynamic subscriptions.
*
* @see ListenerServiceMBean
* @see NotificationFilterFactory
*
* REVISIONS
* =========
* 14/03/05, dimitris
* The filter mechanism has been extended to support specification
* of arbitrary filters, using dynamic filter factory plugins
* implementing the NotificationFilterFactory interface.
* Three filter factories corresponding to the "standard" jmx
* notification filters are supplied by default in package
* org.jboss.system.filterfactory.
*
* 19/10/04, dimitris
* renamed inner class MBeanInfo to SubscriptionInfo and made public,
* using NotificationFilter instead of NotificationFilterSupport and added new
* subscribe(List subscriptionList, boolean dynamicSubscriptions, ObjectName listener)
* to allow external programmatic specification of the subscription list.
*
* 28/02/04, dimitris
* explicit subscribe()/unsubscribe() replaced implicit start()/stop();
* dynamic subscription behaviour can be enabled/disabled, plus it is
* now possible to specify an external MBean as notification listener.
*
* 02/02/04, dimitris
* Initial version, that resulted by generalizing the notification
* subscription mechanism of the snmp adapter.
*
* @author <a href="mailto:dimitris@jboss.org">Dimitris Andreadis</a>
*
* @version $Revision: 81033 $
**/
public abstract class ListenerServiceMBeanSupport
extends ServiceMBeanSupport
implements ListenerServiceMBean, NotificationListener
{
// Private Data --------------------------------------------------
/** The list of mbean subscriptions */
private List sublist; // if null, subscriptions not made
/** The mbean subscription config in XML form */
private Element xmllist; // set through SubscriptionList attribute
/** monitoring and registering to new MBeans, as they appear */
private boolean dynamicSubscriptions;
/** the receiver of the notifications */
private ObjectName listener;
/** Handback to identify our own MBeanServerDelegate subscription */
private Object myHandback;
/** Filter to receive only registration events */
private NotificationFilterSupport myFilter;
/** Has subscribe() been called and unsubscribe not been called? */
private boolean subscribed;
// Constructors -------------------------------------------------
/**
* Constructs a <tt>ListenerServiceMBeanSupport</tt>.
**/
public ListenerServiceMBeanSupport()
{
super();
init();
}
/**
* Constructs a <tt>ListenerServiceMBeanSupport</tt>.
*
* Pass-through to ServiceMBeanSupport.
*
* @param type The class type to determine Logger name from.
**/
public ListenerServiceMBeanSupport(final Class type)
{
super(type);
init();
}
/**
* Constructs a <tt>ListenerServiceMBeanSupport</tt>.
*
* Pass-through to ServiceMBeanSupport.
*
* @param category The logger category name.
**/
public ListenerServiceMBeanSupport(final String category)
{
super(category);
init();
}
/**
* Constructs a <tt>ListenerServiceMBeanSupport</tt>.
*
* Pass-through to ServiceMBeanSupport.
*
* @param log The logger to use.
**/
public ListenerServiceMBeanSupport(final Logger log)
{
super(log);
init();
}
// ListenerServiceMBean Implementation ---------------------------
/**
* Used to configure the JMX notification subscriptions.
*
* The configuration is done inline in the mbean descriptor.
*
* See jboss-subscription.dtd
**/
public void setSubscriptionList(Element list)
{
// deep copy the provided Element for later use
// not sure if really necessary - play it safe
this.xmllist = (Element)list.cloneNode(true);
}
// Public API ----------------------------------------------------
public List<SubscriptionInfo> getSubscriptions()
{
return sublist;
}
public void setSubscriptions(List<SubscriptionInfo> list)
{
this.sublist = list;
}
/**
* Subscribes this MBean for JMX notifications.
*
* @param dynamicSubscriptions indicates whether to monitor and subscribe
* to new MBeans that match the specification.
**/
public void subscribe(boolean dynamicSubscriptions)
throws Exception
{
subscribe(dynamicSubscriptions, this.getServiceName());
}
/**
* Subscribes a listener MBean for JMX notifications.
*
* @param dynamicSubscriptions indicates whether to monitor and subscribe
* to new MBeans that match the specification.
* @param listener the receiver of the notifications.
**/
public void subscribe(boolean dynamicSubscriptions, ObjectName listener)
throws Exception
{
// we need an xml subscription specification
if (this.xmllist != null && this.sublist == null)
{
// Parse the XML spec
log.debug("Parsing subscription specification");
List subscriptionList = parseXMLSubscriptionSpec(this.xmllist);
subscribe(subscriptionList, dynamicSubscriptions, listener);
}
else if (this.sublist != null)
{
subscribe(sublist, dynamicSubscriptions, listener);
}
else
log.debug("Subscription specification not provided");
}
/**
* Subscribes a listener MBean for JMX notifications.
*
* @param subscriptionList the list containing SubscriptionInfo data.
* @param dynamicSubscriptions indicates whether to monitor and subscribe
* to new MBeans that match the specification.
* @param listener the receiver of the notifications.
**/
public void subscribe(List subscriptionList, boolean dynamicSubscriptions, ObjectName listener)
throws Exception
{
// return if already subscribed
if (subscribed)
return;
// we need an subscription specification
if (subscriptionList != null)
{
// store input
this.sublist = subscriptionList;
this.dynamicSubscriptions = dynamicSubscriptions;
this.listener = listener;
log.debug(this.sublist);
log.debug("Subscribing for JMX notifications" +
", dynamic=" + dynamicSubscriptions +
(this.getServiceName().equals(listener) ? "" :
", listener='" + listener + "'"));
bulkRegister();
if (dynamicSubscriptions == true)
{
// Subscribe to MBeanServerDelegate MBean for registrations
getServer().addNotificationListener(
new ObjectName(ServerConstants.MBEAN_SERVER_DELEGATE),
this.getServiceName(),
this.myFilter,
this.myHandback
);
log.debug("Subscribed to MBeanServerDelegate, too");
}
subscribed = true;
}
else
log.debug("Subscription list not provided");
}
/**
* Unsubscribes for JMX notifications
**/
public void unsubscribe()
{
// return if not subscribed
if (!subscribed)
return;
log.debug("Removing all JMX notification subscriptions");
bulkUnregister();
if (this.dynamicSubscriptions == true)
{
// Unbscribe from MBeanServerDelegate MBean for registrations
try {
getServer().removeNotificationListener(
new ObjectName(ServerConstants.MBEAN_SERVER_DELEGATE),
this.getServiceName(),
this.myFilter,
this.myHandback
);
log.debug("Unsubscribed from MBeanServerDelegate, too");
}
catch (MalformedObjectNameException e)
{
// shouldn't happen!
log.warn("Could not convert '" + ServerConstants.MBEAN_SERVER_DELEGATE
+ "' to ObjectName", e);
}
catch (InstanceNotFoundException e)
{
// shouldn't happen
log.warn("Could not unsubscribe from non-existent MBeanServerDelegate!", e);
}
catch (ListenerNotFoundException e)
{
// shouldn't happend
log.warn("Could not unsubscribe from MBeanServerDelegate", e);
}
}
// indicate we've unsubscribed
this.subscribed = false;
}
// NotificationListener -----------------------------------------
/**
* DO NOT OVERRIDE THIS!
*
* Handles dynamic subscriptions before delegating to
* handleNotification2()
**/
public void handleNotification(Notification notification, Object handback)
{
// check if the notification is for me!
if (this.dynamicSubscriptions == true && handback == this.myHandback)
{
if (log.isTraceEnabled())
log.trace("It's for me: " + notification + ", handback:" + handback);
String type = notification.getType();
ObjectName target = null;
try {
target = ((MBeanServerNotification)notification).getMBeanName();
}
catch (ClassCastException e) {
log.warn("MBeanServer sent unknown notification class type: " +
notification.getClass().getName());
return;
}
if (type.equals(MBeanServerNotification.REGISTRATION_NOTIFICATION))
{
// iterate over the subscription specification
Iterator i = this.sublist.iterator();
while (i.hasNext())
{
SubscriptionInfo mbeanInfo = (SubscriptionInfo)i.next();
ObjectName objectName = mbeanInfo.getObjectName();
try
{
if(objectName.apply(target))
{
log.debug("ObjectName: '" + target + "' matched '" + objectName + "'");
// go for it!
singleRegister(
this.getServer(),
target,
this.listener,
mbeanInfo.getFilter(),
mbeanInfo.getHandback()
);
}
}
catch (Exception e)
{
// catch exceptions from apply()
// shouldn't happen
log.warn("Caught exception from ObjectName.apply("
+ target + ")", e);
}
}
}
else
{
log.warn("Got unknown notification type from MBeanServerDelegate: "
+ type);
}
}
else // delegate to subclass
handleNotification2(notification, handback);
}
/**
* Override to add notification handling!
**/
public void handleNotification2(Notification notification, Object handback)
{
// empty!
}
// Private Methods -----------------------------------------------
/**
* Initialises myself
**/
private void init()
{
// just pickup a unique object
this.myHandback = new Integer(Integer.MAX_VALUE);
// allow only registration events
this.myFilter = new NotificationFilterSupport();
this.myFilter.enableType(MBeanServerNotification.REGISTRATION_NOTIFICATION);
}
/**
* Subscribes for notifications to a single MBean
**/
private void singleRegister(
MBeanServer server, ObjectName target, ObjectName listener,
NotificationFilter filter, Object handback)
{
try
{
server.addNotificationListener(target, listener, filter, handback);
logSubscription(target, listener, handback, filter);
}
catch (InstanceNotFoundException e)
{
// ignore - mbean might not be registered
log.debug("Could not subscribe to: '" + target
+ "', target or listener MBean not registered");
}
catch (RuntimeException e)
{
log.warn("Failed to subscribe to: '" + target
+ "', maybe not a notification broadcaster or: '" + listener
+ "', maybe not a notification listener");
}
}
/**
* Unsubscribes for notifications from a single MBean
**/
private void singleUnregister(
MBeanServer server, ObjectName target, ObjectName listener,
NotificationFilter filter, Object handback)
{
try
{
// remove the matching subscription
server.removeNotificationListener(target, listener, filter, handback);
log.debug("Unsubscribed from: '" + target + "'");
}
catch (InstanceNotFoundException e)
{
// ignore - target mbean not present
log.debug("Could not unsubscribe from non-existent: '"
+ target + "'");
}
catch (ListenerNotFoundException e)
{
// May happen if target is not a notification broadcaster
// and so we hadn't registered in the first place
log.debug("Could not unsubscribe from: '" + target + "'");
}
catch (RuntimeException e)
{
// whatever
log.debug("Could not unsubscribe from: '" + target + "'");
}
}
/**
* Performs the notification subscriptions
**/
private void bulkRegister()
{
// iterate over the subscription specification
Iterator i = this.sublist.iterator();
// find out my server
MBeanServer server = this.getServer();
while (i.hasNext())
{
SubscriptionInfo mbeanInfo = (SubscriptionInfo)i.next();
ObjectName objectName = mbeanInfo.getObjectName();
Object handback = mbeanInfo.getHandback();
NotificationFilter filter = mbeanInfo.getFilter();
if (objectName.isPattern())
{
Set mset = server.queryNames(objectName, null);
log.debug("ObjectName: '" + objectName + "' matched " + mset.size() + " MBean(s)");
Iterator j = mset.iterator();
while (j.hasNext())
singleRegister(server, (ObjectName)j.next(), this.listener,
filter, handback);
}
else
singleRegister(server, objectName, this.listener, filter, handback);
}
}
/**
* Performs bulk unregistration
**/
private void bulkUnregister()
{
// iterate over the subscription specification
Iterator i = this.sublist.iterator();
// find out my server
MBeanServer server = this.getServer();
while (i.hasNext())
{
SubscriptionInfo mbeanInfo = (SubscriptionInfo)i.next();
ObjectName objectName = mbeanInfo.getObjectName();
Object handback = mbeanInfo.getHandback();
NotificationFilter filter = mbeanInfo.getFilter();
if (objectName.isPattern())
{
Set mset = server.queryNames(objectName, null);
log.debug("ObjectName: '" + objectName + "' matched " + mset.size() + " MBean(s)");
Iterator j = mset.iterator();
while (j.hasNext())
singleUnregister(server, (ObjectName)j.next(), this.listener,
filter, handback);
}
else
singleUnregister(server, objectName, this.listener, filter, handback);
}
}
/**
* Logs subscription info
**/
private void logSubscription(
ObjectName objectName, ObjectName listener,
Object handback, NotificationFilter filter)
{
StringBuffer sbuf = new StringBuffer(100);
sbuf.append("Subscribed to: { objectName='").append(objectName);
sbuf.append("', listener='").append(listener);
sbuf.append("', handback=").append(handback);
sbuf.append(", filter=");
sbuf.append(filter == null ? null : filter.toString());
sbuf.append(" }");
log.debug(sbuf.toString());
}
/**
* Encapsulte the factory and filter creation logic
*/
private NotificationFilter createNotificationFilter(String factoryClass, Element filterConfig)
throws Exception
{
NotificationFilterFactory factory;
try
{
// try to load the factory Class
Class clazz = Thread.currentThread().getContextClassLoader().loadClass(factoryClass);
factory = (NotificationFilterFactory)clazz.newInstance();
}
catch (Exception e) // ClassNotFoundException, IllegalAccessException, InstantiationException
{
// factory class not found. Make a second try using
// the 'org.jboss.system.filterfactory.' package prefix
// for the "standard" filter factories provided with jboss.
// If that fails, too, rethrow the original exception.
try
{
factoryClass = "org.jboss.system.filterfactory." + factoryClass;
Class clazz = Thread.currentThread().getContextClassLoader().loadClass(factoryClass);
factory = (NotificationFilterFactory)clazz.newInstance();
}
catch (Exception inner)
{
throw e;
}
}
// delegate the filter creation/configuration to the factory
return factory.createNotificationFilter(filterConfig);
}
/**
* Parses the XML subscription specification
**/
private ArrayList parseXMLSubscriptionSpec(Element root)
throws Exception
{
ArrayList slist = new ArrayList();
// parse level 0 - subscription-list
if (!root.getNodeName().equals(SL_ROOT_ELEMENT))
{
throw new Exception("Expected '" + SL_ROOT_ELEMENT + "' element, "
+ "got: " + "'" + root.getNodeName() + "'");
}
else
{
NodeList rootlist = root.getChildNodes();
for (int i = 0; i < rootlist.getLength(); i++)
{
// Parse level 1 - look for mbeans
Node mbean = rootlist.item(i);
if (mbean.getNodeName().equals(SL_MBEAN_ELEMENT))
{
// mbean found look for name & handback attrs
String name = null;
if (((Element)mbean).hasAttribute(SL_MBEAN_NAME_ATTRIBUTE))
{
name = ((Element)mbean).getAttribute(SL_MBEAN_NAME_ATTRIBUTE);
}
else
{
throw new Exception("'" + SL_MBEAN_ELEMENT + "' element must have a '"
+ SL_MBEAN_NAME_ATTRIBUTE + "' attribute");
}
String handback = null;
if (((Element)mbean).hasAttribute(SL_MBEAN_HANDBACK_ATTRIBUTE))
{
handback = ((Element)mbean).getAttribute(SL_MBEAN_HANDBACK_ATTRIBUTE);
}
// try to convert name to the correct data type
// may throw MalformedObjectNameException
ObjectName objectName = new ObjectName(name);
// Parse level 2 - see if we have a filter for this subscription
NotificationFilter filter = null;
NodeList mbeanChildren = mbean.getChildNodes();
// check for filter spec, as a single mbean child node
for (int j = 0; j < mbeanChildren.getLength(); j++)
{
Node mbeanChildNode = mbeanChildren.item(j);
// check if this is a 'filter' node
if (mbeanChildNode.getNodeName().equals(SL_FILTER_ELEMENT))
{
// look for the 'factory' attribute
String factory = null;
if (((Element)mbeanChildNode).hasAttribute(SL_FILTER_FACTORY_ATTRIBUTE))
{
factory = ((Element)mbeanChildNode).getAttribute(SL_FILTER_FACTORY_ATTRIBUTE);
// instantiate the factory and request the filter
filter = createNotificationFilter(factory, (Element)mbeanChildNode);
break;
}
else
{
throw new Exception("'" + SL_FILTER_ELEMENT + "' element must have a '"
+ SL_FILTER_FACTORY_ATTRIBUTE + "' attribute");
}
}
}
if (filter == null)
{
// if no filter has been set check for old-style
// <notification type="..."/> construct that results
// in a fixed NotificationFilterSupport filter
// need to find out all notification types (if any)
// in order to create the Notification filter
ArrayList tmplist = new ArrayList(mbeanChildren.getLength());
for (int j = 0; j < mbeanChildren.getLength(); j++)
{
Node mbeanChildNode = mbeanChildren.item(j);
// check if this is a 'notification' element
if (mbeanChildNode.getNodeName().equals(SL_NOTIFICATION_ELEMENT))
{
// look for 'type' attribute
String type = null;
if (((Element)mbeanChildNode).hasAttribute(SL_NOTIFICATION_TYPE_ATTRIBUTE))
{
type = ((Element)mbeanChildNode).getAttribute(SL_NOTIFICATION_TYPE_ATTRIBUTE);
tmplist.add(type);
}
else
{
throw new Exception("'" + SL_NOTIFICATION_ELEMENT + "' element must have a '"
+ SL_NOTIFICATION_TYPE_ATTRIBUTE + "' attribute");
}
}
}
// create the filter (if needed)
if (tmplist.size() > 0)
{
NotificationFilterSupport sfilter = new NotificationFilterSupport();
for (int j = 0; j < tmplist.size(); j++)
{
sfilter.enableType((String)tmplist.get(j));
}
filter = sfilter;
}
}
slist.add(new SubscriptionInfo(objectName, handback, filter));
}
}
}
return slist;
}
// Inner Class ---------------------------------------------------
/**
* Inner data holder class to store the parsed subscription specification.
**/
public static final class SubscriptionInfo
{
// Private Data -----------------------------------------------
/** MBean notification broadcaster or pattern */
private ObjectName objectName;
/** Optional handback object to identify a subscription */
private Object handback;
/** Arbitrary NotificationFilter */
private NotificationFilter filter;
// Constructor ------------------------------------------------
/**
* Simple CTOR
**/
public SubscriptionInfo(ObjectName objectName, Object handback, NotificationFilter filter)
{
this.objectName = objectName;
this.handback = handback;
this.filter = filter;
}
// Accessors --------------------------------------------------
/**
* Gets objectname
**/
public ObjectName getObjectName()
{
return this.objectName;
}
/**
* Gets handback object
**/
public Object getHandback()
{
return this.handback;
}
/**
* Gets notification filter
**/
public NotificationFilter getFilter()
{
return this.filter;
}
/**
* Pretty prints
**/
public String toString()
{
StringBuffer sbuf = new StringBuffer(100);
sbuf.append("SubscriptionInfo { objectName='").append(this.objectName);
sbuf.append("', handback=").append(this.handback);
sbuf.append(", filter=");
sbuf.append(this.filter == null ? null : this.filter.toString());
sbuf.append(" }");
return sbuf.toString();
}
}
}