/**
* Helios, OpenSource Monitoring
* Brought to you by the Helios Development Group
*
* Copyright 2007, 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.jmx.notifications;
import java.io.Closeable;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.management.MBeanServer;
import javax.management.MBeanServerConnection;
import javax.management.MBeanServerDelegate;
import javax.management.MBeanServerNotification;
import javax.management.Notification;
import javax.management.NotificationFilter;
import javax.management.NotificationFilterSupport;
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.helios.apmrouter.jmx.JMXHelper;
/**
* <p>Title: NotificationListenerControl</p>
* <p>Description: </p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.jmx.notifications.NotificationListenerControl</code></p>
*/
public class NotificationListenerControl implements Closeable, NotificationListener, NotificationFilter {
/** */
private static final long serialVersionUID = 4828378016083478296L;
/** The MBeanServer the listener will be registered with */
protected final MBeanServerConnection connection;
/** The ObjectName of the MBean[s] the listener will be registered with */
protected final ObjectName objectName;
/** Indicates if the target ObjectName is a wildcard */
protected final boolean wildCard;
/** Indicates if the target connection is local (i.e. implements MBeanServer) */
protected final boolean localConnection;
/** Indicates that the listener registration object name set is final and suppresses the listener for new matching object names */
protected final boolean finalSub;
/** The delegate NotificationListener */
protected final NotificationListener listenerDelegate;
/** The delegate NotificationFilter */
protected final NotificationFilter filterDelegate;
/** The subscribed ObjectName subscription set */
protected final Set<ObjectName> subscriptionSet;
/** The optional handback to return to the listener */
protected final Object handback;
/** The JMXConnector providing the MBeanServer the listener will be registered with, which if provided will be closed on a call to {@link #close()} */
protected JMXConnector connector = null;
/**
* Sets a JMXConnector which, if not null, will be closed on a call to {@link #close()}
* @param connector the connector to close
*/
protected void setJMXConnector(JMXConnector connector) {
this.connector = connector;
}
/** A notification listener registered to listen on new MBean notifications */
protected final NotificationListener newMBeanListener = new NotificationListener() {
/**
* {@inheritDoc}
* @see javax.management.NotificationListener#handleNotification(javax.management.Notification, java.lang.Object)
*/
@Override
public void handleNotification(Notification notification, Object handback) {
if(notification instanceof MBeanServerNotification) {
MBeanServerNotification mbsn = (MBeanServerNotification)notification;
if(!subscriptionSet.contains(mbsn.getMBeanName())) {
synchronized(subscriptionSet) {
if(!subscriptionSet.contains(mbsn.getMBeanName())) {
subscribe(mbsn.getMBeanName());
}
}
}
}
}
};
/** A filter to allow only new MBean publication event notifications */
protected static final NotificationFilterSupport newMBeanNotificationFilter = new NotificationFilterSupport();
static {
newMBeanNotificationFilter.disableAllTypes();
newMBeanNotificationFilter.enableType(MBeanServerNotification.REGISTRATION_NOTIFICATION);
}
/**
* Creates a new NotificationListenerControl
* @param connection The MBeanServer the listener will be registered with
* @param objectName The ObjectName of the MBean[s] the listener will be registered with
* @param finalSub Indicates that the listener registration object name set is final and suppresses the listener for new matching object names
* @param listenerDelegate The delegate NotificationListener
* @param filterDelegate The delegate NotificationFilter
* @param handback The optional handback to return to the listener
*/
protected NotificationListenerControl(MBeanServerConnection connection,
ObjectName objectName, boolean finalSub,
NotificationListener listenerDelegate,
NotificationFilter filterDelegate,
Object handback) {
this.connection = connection;
localConnection = this.connection instanceof MBeanServer;
this.objectName = objectName;
this.finalSub = finalSub;
this.listenerDelegate = listenerDelegate;
this.filterDelegate = filterDelegate;
wildCard = this.objectName.isPattern();
subscriptionSet = wildCard ? Collections.synchronizedSet(new HashSet<ObjectName>()) : null;
this.handback = handback;
if(wildCard && !this.finalSub) {
try {
connection.addNotificationListener(MBeanServerDelegate.DELEGATE_NAME, this, newMBeanNotificationFilter, null);
} catch (Exception ex) {
ex.printStackTrace(System.err);
}
}
}
/**
* Subscribes this listener to the MBean identified by the passed ObjectName
* @param on The objetc name of the MBean to subscribe to notifications from
*/
protected void subscribe(ObjectName on) {
Set<ObjectName> subscribed = on.isPattern() ? new HashSet<ObjectName>() : new HashSet<ObjectName>(Arrays.asList(on));
try {
Set<ObjectName> objectNames = on.isPattern() ? new HashSet<ObjectName>() : new HashSet<ObjectName>(Arrays.asList(on));
for(ObjectName objectName : objectNames) {
if(!subscriptionSet.contains(objectName)) {
synchronized(subscriptionSet) {
if(!subscriptionSet.contains(objectName)) {
connection.addNotificationListener(objectName, this, filterDelegate, handback);
subscribed.add(objectName);
}
}
}
}
this.subscriptionSet.addAll(subscribed);
} catch (Exception ex) {
if(!subscribed.isEmpty()) unsubAll(subscribed);
throw new RuntimeException("Failed to subscribe to [" + on + "]", ex);
}
}
/**
* Unsubscribes this listener from all the passed object names
* @param objectNames The object names to unsubscribe from
*/
protected void unsubAll(Set<ObjectName> objectNames) {
for(ObjectName objectName : objectNames) {
try { connection.removeNotificationListener(objectName, this); } catch (Exception e) {}
}
}
/**
* {@inheritDoc}
* @see javax.management.NotificationFilter#isNotificationEnabled(javax.management.Notification)
*/
@Override
public boolean isNotificationEnabled(Notification notification) {
return filterDelegate==null ? true : filterDelegate.isNotificationEnabled(notification);
}
/**
* {@inheritDoc}
* @see javax.management.NotificationListener#handleNotification(javax.management.Notification, java.lang.Object)
*/
@Override
public void handleNotification(Notification notification, Object handback) {
listenerDelegate.handleNotification(notification, handback);
}
/**
* {@inheritDoc}
* @see java.io.Closeable#close()
*/
@Override
public void close() throws IOException {
for(ObjectName on: subscriptionSet) {
try { connection.removeNotificationListener(on, this); } catch (Exception e) {}
}
if(wildCard && !finalSub) {
try { connection.removeNotificationListener(MBeanServerDelegate.DELEGATE_NAME, this.newMBeanListener); } catch (Exception e) {}
}
if(connector!=null) {
try { connector.close(); } catch (Exception ex) {}
}
}
/**
* Creates a new builder
* @param objectName The ObjectName of the MBean[s] the listener will be registered with
* @param listenerDelegate The delegate NotificationListener
* @return a new builder
*/
public static Builder builder(ObjectName objectName,
NotificationListener listenerDelegate) {
return new Builder(objectName, listenerDelegate);
}
/**
* <p>Title: Builder</p>
* <p>Description: A fluent style builder for {@link NotificationListenerControl}s</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.jmx.notifications.NotificationListenerControl.Builder</code></p>
*/
public static class Builder {
/** The ObjectName of the MBean[s] the listener will be registered with */
protected final ObjectName objectName;
/** The delegate NotificationListener */
protected final NotificationListener listenerDelegate;
// =============
// Optional
// =============
/** The MBeanServer the listener will be registered with */
protected MBeanServerConnection connection;
/** The JMXConnection providing the MBeanServer the listener will be registered with */
protected JMXConnector connector = null;
/** Indicates that the listener registration object name set is final and suppresses the listener for new matching object names */
protected boolean finalSub = false;
/** The delegate NotificationFilter */
protected NotificationFilter filterDelegate;
/** Notification type filter ins */
protected final Set<String> filterIns = new HashSet<String>();
/** Notification type filter outs */
protected final Set<String> filterOuts = new HashSet<String>();
/** The optional handback to return to the listener */
protected Object handback;
/** The jmx service url to connect to */
protected String jmxServiceUrl;
/** The jmx credentials to use when connecting */
protected String[] credentials;
/**
* Creates a new Builder. If no connection or remote url is provided, will connect to the local helios mbeanserver.
* @param objectName The ObjectName of the MBean[s] the listener will be registered with
* @param listenerDelegate The delegate NotificationListener
*/
protected Builder(ObjectName objectName,
NotificationListener listenerDelegate) {
this.objectName = objectName;
this.listenerDelegate = listenerDelegate;
}
/**
* Builds a new {@link NotificationListenerControl}
* @return a new {@link NotificationListenerControl}
*/
public NotificationListenerControl build() {
NotificationFilter nf = null;
if(filterDelegate!=null) {
nf = filterDelegate;
} else {
if(!filterIns.isEmpty() || !filterOuts.isEmpty()) {
NotificationFilterSupport nfs = new NotificationFilterSupport();
for(String prefix: filterIns) {
nfs.enableType(prefix);
}
for(String prefix: filterOuts) {
nfs.disableType(prefix);
}
nf = nfs;
}
}
NotificationListenerControl nlc = new NotificationListenerControl(getMBeanServerConnection(), objectName, finalSub, listenerDelegate, nf, handback);
nlc.setJMXConnector(connector);
return nlc;
}
/**
* Acquires the MBeanServerConnection defined by this builders config
* @return the MBeanServerConnection
*/
protected MBeanServerConnection getMBeanServerConnection() {
if(connection!= null) return connection;
if(jmxServiceUrl!=null && !jmxServiceUrl.trim().isEmpty()) {
Map<String, ?> environment = credentials==null ? null : Collections.singletonMap(JMXConnector.CREDENTIALS, credentials);
try {
JMXConnectorFactory.connect(new JMXServiceURL(jmxServiceUrl), environment);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
return JMXHelper.getHeliosMBeanServer();
}
/**
* Sets the optional handback to return to the listener
* @param handback optional handback to return to the listener
* @return this builder
*/
public Builder handback(Object handback) {
this.handback = handback;
return this;
}
/**
* Sets the JMX service URL of the remote MBeanServer to connect to
* @param jmxServiceUrl the the JMX service URL of the remote MBeanServer to connect to
* @return this builder
*/
public Builder jmxServiceUrl(String jmxServiceUrl) {
this.jmxServiceUrl = jmxServiceUrl.trim();
return this;
}
/**
* Sets the credentials to connect with
* @param userName The username
* @param password The password
* @return this builder
*/
public Builder credentials(String userName, String password) {
credentials = new String[]{userName, password};
return this;
}
/**
* Sets the connection to register listeners with
* @param connection the connection to register listeners with
* @return this builder
*/
public Builder connection(MBeanServerConnection connection) {
this.connection = connection;
return this;
}
/**
* Sets the connector to acquire the MBeanServerConnection from
* @param connector the connector to acquire the MBeanServerConnection from
* @return this builder
*/
public Builder connector(JMXConnector connector) {
this.connector = connector;
return this;
}
/**
* Sets a notification filter
* @param filterDelegate the filterDelegate to set
* @return this builder
*/
public Builder filter(NotificationFilter filterDelegate) {
this.filterDelegate = filterDelegate;
return this;
}
/**
* Converts the passed charsequence array to trimmed strings and adds them to the target set
* @param target The target set to add to
* @param filters The filter prefixes to add
*/
protected void filters(final Set<String> target, CharSequence...filters) {
if(filters!=null && filters.length!=0) {
for(CharSequence f: filters) {
if(f==null) continue;
String fs = f.toString().trim();
if(fs.isEmpty()) continue;
target.add(fs);
}
}
}
/**
* Adds the passed filters to the filter ins or notification type prefixes that should match
* @param filters The filters to add
* @return this builder
*/
public Builder filterIn(CharSequence...filters) {
filters(filterIns, filters);
return this;
}
/**
* Adds the passed filters to the filter outs or notification type prefixes that should not match
* @param filters The filters to add
* @return this builder
*/
public Builder filterOut(CharSequence...filters) {
filters(filterOuts, filters);
return this;
}
/**
* Sets a flag indicating that the initial subscription set of MBeans is final
* @param finalSub the finalSub to set
* @return this builder
*/
public Builder finalSub(boolean finalSub) {
this.finalSub = finalSub;
return this;
}
}
}