/** * Helios, OpenSource Monitoring * Brought to you by the Helios Development Group * * Copyright 2012, Helios Development Group 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.helios.apmrouter.subscription.impls.jmx; import java.util.Arrays; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.atomic.AtomicLong; import javax.management.MBeanServerConnection; import javax.management.MBeanServerDelegate; import javax.management.MBeanServerNotification; import javax.management.Notification; import javax.management.NotificationBroadcasterSupport; import javax.management.NotificationFilter; import javax.management.NotificationListener; import javax.management.ObjectName; import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServiceURL; import org.apache.log4j.Logger; import org.helios.apmrouter.dataservice.json.JsonRequest; import org.helios.apmrouter.subscription.criteria.FailedCriteriaResolutionException; import org.helios.apmrouter.subscription.criteria.SubscriptionCriteria; import org.helios.apmrouter.subscription.criteria.SubscriptionCriteriaInstance; import org.helios.apmrouter.subscription.criteria.builder.SubscriptionCriteriaBuilder; import org.helios.apmrouter.subscription.session.SubscriptionSession; import org.helios.apmrouter.util.SystemClock; /** * <p>Title: JMXSubscriptionCriteriaInstance</p> * <p>Description: A resolved and active instance of a {@link JMXSubscriptionCriteria}</p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.apmrouter.subscription.jmx.JMXSubscriptionCriteriaInstance</code></p> */ public class JMXSubscriptionCriteriaInstance implements SubscriptionCriteria<String, ObjectName, NotificationFilter>, SubscriptionCriteriaInstance<Notification>, NotificationListener, NotificationFilter { /** */ private static final long serialVersionUID = -8406458051126347970L; /** The criteria this instance was resolved from */ protected final JMXSubscriptionCriteria criteria; /** The original json request issued for this subscription */ protected final JsonRequest request; /** The JMXConnector used to connect to the MBeanServer */ protected JMXConnector jmxConnector = null; /** The managing subscription session */ protected SubscriptionSession session = null; /** The target MBeanServer for this subscription */ protected MBeanServerConnection mbeanServerConnection = null; /** A set of ObjectNames this criteria instance is activated for */ protected final Set<ObjectName> objectNames = new CopyOnWriteArraySet<ObjectName>(); /** A set of ObjectNames this criteria failed to activated for */ protected final Set<ObjectName> failedObjectNames = new CopyOnWriteArraySet<ObjectName>(); /** The arbitrary criteria key */ protected Object subscriptionKey = null; /** The assigned internal serial number for this subscription */ protected final Long jmxSubId; /** A serial number generator used to register callbacks so notifications can quickly be identified as being for this subscription */ protected static final AtomicLong serial = new AtomicLong(0); /** Static class logger */ protected static final Logger LOG = Logger.getLogger(JMXSubscriptionCriteriaInstance.class); /** * Creates a new JMXSubscriptionCriteriaInstance * @param criteria The criteria this instance will be resolved from * @param request The original json request issued for this subscription * @param subscriptionKey The arbitrary subscription key */ JMXSubscriptionCriteriaInstance(JMXSubscriptionCriteria criteria, JsonRequest request, Object subscriptionKey) { super(); this.criteria = criteria; this.request = request; jmxSubId = serial.incrementAndGet(); this.subscriptionKey = subscriptionKey; } /** * Resolves the criteria. * @param session The managing subscription session. * @throws FailedCriteriaResolutionException */ public void resolve(SubscriptionSession session) throws FailedCriteriaResolutionException { try { JMXServiceURL serviceURL = new JMXServiceURL(criteria.getEventSource()); jmxConnector = JMXConnectorFactory.connect(serviceURL); this.session = session; mbeanServerConnection = jmxConnector.getMBeanServerConnection(); LOG.info("Resolved JMX Criteria MBeanServer [" + serviceURL + "]"); } catch (Exception ex) { throw new FailedCriteriaResolutionException(criteria, "Failed to locate MBeanServerConnection for [" + criteria.getEventSource() + "]", ex); } if(criteria.isPattern()) { try { mbeanServerConnection.addNotificationListener(MBeanServerDelegate.DELEGATE_NAME , this, null, new long[]{jmxSubId, session.getSubscriptionSessionId()}); LOG.info("Added MBean Registration Listener on JMX Criteria MBeanServer [" + criteria.getEventSource() + "]"); } catch (Exception ex) { LOG.error("Failed to add MBean registration listener for JMX criteria [" + criteria.getEventSource() + "]", ex); throw new FailedCriteriaResolutionException(criteria, "Failed to add MBean registration listener for pattern [" + criteria.getEventSource() + "]", ex); } try { for(ObjectName on: mbeanServerConnection.queryNames(criteria.getEventFilter(), null)) { addMBeanSource(on); } } catch (Exception ex) { LOG.error("Failed to process existing matching MBeans for [" + criteria.getEventFilter() + "] on MBeanServer [" + criteria.getEventSource() + "]", ex); throw new FailedCriteriaResolutionException(criteria, "Failed to process existing matching MBeans for [" + criteria.getEventFilter() + "] on MBeanServer [" + criteria.getEventSource() + "]", ex); } } else { try { addMBeanSource(criteria.getEventFilter()); } catch (Exception ex) { LOG.error("Failed to add notification listener for non-pattern MBean [" + criteria.getEventFilter() + "]", ex); throw new FailedCriteriaResolutionException(criteria, "Failed to add notification listener for non-pattern MBean [" + criteria.getEventFilter() + "]", ex); } } } /** * {@inheritDoc} * @see org.helios.apmrouter.subscription.criteria.SubscriptionCriteriaInstance#getSubscriptionCriteria() */ @Override public SubscriptionCriteria<String, ObjectName, NotificationFilter> getSubscriptionCriteria() { return criteria; } /** * Terminates this criteria instance */ @Override public void terminate() { objectNames.addAll(failedObjectNames); failedObjectNames.clear(); for(ObjectName on: objectNames) { try { mbeanServerConnection.removeNotificationListener(on, this, criteria.getEventExtendedFilter(), jmxSubId); } catch (Exception ex) { /* NoOp */ } } objectNames.clear(); try { jmxConnector.close(); } catch (Exception e) { /* NoOp */ } } /** * {@inheritDoc} * @see javax.management.NotificationFilter#isNotificationEnabled(javax.management.Notification) */ @Override public boolean isNotificationEnabled(Notification notification) { // Test for newly registered or unregistered target MBeans // If so, add or remove, then return false. if(notification instanceof MBeanServerNotification) { if(!criteria.isPattern()) return false; MBeanServerNotification msn = (MBeanServerNotification)notification; if(criteria.getEventFilter().apply(msn.getMBeanName())) { if(MBeanServerNotification.REGISTRATION_NOTIFICATION.equals(msn.getType())) { addMBeanSource(msn.getMBeanName()); } else { objectNames.remove(msn.getMBeanName()); failedObjectNames.remove(msn.getMBeanName()); } } return false; } return true; } /** * {@inheritDoc} * @see javax.management.NotificationListener#handleNotification(javax.management.Notification, java.lang.Object) */ @Override public void handleNotification(Notification notification, Object handback) { if(handback instanceof long[]) { long[] key = (long[])handback; if(key.length==2) { if(jmxSubId.equals(key[0]) && session.getSubscriptionSessionId()==key[1]) { request.subResponse("" + jmxSubId).setContent(notification).send(request.getChannel()); return; } } } LOG.warn("No match for notification [" + notification + " Handback:" + handback); } /** * {@inheritDoc} * @see org.helios.apmrouter.subscription.criteria.SubscriptionCriteriaInstance#getCriteriaId() */ @Override public long getCriteriaId() { return jmxSubId; } /** * Adds an MBean's ObjectName to the subscribed notification set. * If the addition of the notification listener fails, the object name * is removed from the subscribed set and added to the failed set. * @param mbean */ protected void addMBeanSource(ObjectName mbean) { if(objectNames.add(mbean)) { try { mbeanServerConnection.addNotificationListener(mbean, this, criteria.getEventExtendedFilter(), new long[]{jmxSubId, session.getSubscriptionSessionId()}); } catch (Exception ex) { LOG.warn("Failed to add notification listener for new MBean [" + mbean + "]", ex); objectNames.remove(mbean); failedObjectNames.add(mbean); } } } /** * {@inheritDoc} * @see org.helios.apmrouter.subscription.criteria.SubscriptionCriteria#getEventSource() */ public String getEventSource() { return criteria.getEventSource(); } /** * {@inheritDoc} * @see org.helios.apmrouter.subscription.criteria.SubscriptionCriteria#getEventFilter() */ public ObjectName getEventFilter() { return criteria.getEventFilter(); } /** * {@inheritDoc} * @see org.helios.apmrouter.subscription.criteria.SubscriptionCriteria#getEventExtendedFilter() */ public NotificationFilter getEventExtendedFilter() { return criteria.getEventExtendedFilter(); } /** * Indicates if the event filter of the criteria is a pattern or absolute * @return true if the event filter of the criteria is a pattern, false otherwise */ public boolean isPattern() { return criteria.isPattern(); } /** * {@inheritDoc} * @see org.helios.apmrouter.subscription.criteria.SubscriptionCriteria#instantiate(org.helios.apmrouter.dataservice.json.JsonRequest) */ @Override public SubscriptionCriteriaInstance<Notification> instantiate(JsonRequest request) { return this; } /** * {@inheritDoc} * @see org.helios.apmrouter.subscription.criteria.SubscriptionCriteria#getBuilder() */ @Override public SubscriptionCriteriaBuilder getBuilder() { return criteria.getBuilder(); } /** * {@inheritDoc} * @see org.helios.apmrouter.subscription.criteria.SubscriptionCriteria#getSubcriptionKey() */ @Override public Object getSubcriptionKey() { return subscriptionKey; } /** * {@inheritDoc} * @see org.helios.apmrouter.subscription.criteria.SubscriptionCriteria#setSubcriptionKey(java.lang.Object) */ public void setSubcriptionKey(Object subscriptionKey) { this.subscriptionKey = subscriptionKey; } }