Package org.jboss.system

Source Code of org.jboss.system.ListenerServiceMBeanSupport

/*
* 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();
      }
   }
}    
TOP

Related Classes of org.jboss.system.ListenerServiceMBeanSupport

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.