/**
* 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.gmx;
import groovy.lang.Closure;
import groovy.lang.GroovyObject;
import groovy.lang.GroovyShell;
import groovy.lang.GroovySystem;
import groovy.lang.MetaClass;
import groovy.lang.Script;
import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.IntrospectionException;
import javax.management.InvalidAttributeValueException;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MBeanServerConnection;
import javax.management.MBeanServerFactory;
import javax.management.NotCompliantMBeanException;
import javax.management.Notification;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.QueryExp;
import javax.management.ReflectionException;
import javax.management.remote.JMXConnectionNotification;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import org.helios.gmx.classloading.ByteCodeRepository;
import org.helios.gmx.classloading.ReverseClassLoader;
import org.helios.gmx.jmx.ClosureWrappingNotificationFilter;
import org.helios.gmx.jmx.ClosureWrappingNotificationListener;
import org.helios.gmx.jmx.ObjectNameAwareListener;
import org.helios.gmx.jmx.RuntimeMBeanServer;
import org.helios.gmx.jmx.RuntimeMBeanServerConnection;
import org.helios.gmx.util.ClosureDehydrator;
import org.helios.gmx.util.JMXHelper;
import org.helios.vm.VirtualMachine;
import org.helios.vm.VirtualMachineBootstrap;
import org.helios.vm.VirtualMachineDescriptor;
/**
* <p>Title: Gmx</p>
* <p>Description: A factory for {@link javax.management.MBeanServerConnection}s and invocation facade.</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.gmx.Gmx</code></p>
* TODO:
* MBeanServer[Connection] by JMXServiceURL, JMXServiceURL String and protocol components.
* Auto Reconnect
* Authentication for remote connections
* attrs: just values, name/value pairs, set
*/
public class Gmx implements GroovyObject, MBeanServerConnection, NotificationListener {
/** The wrapped MBeanServer connection */
protected RuntimeMBeanServerConnection mbeanServerConnection;
/** The wrapped MBeanServer populated if the connection is an MBeanServer */
protected RuntimeMBeanServer mbeanServer;
/** The JMXConnector for remote connections */
protected JMXConnector connector = null;
/** The JMXConnector's originating service URL */
protected JMXServiceURL serviceURL = null;
/** The JMXConnector environment */
protected final Map<String, ?> environment = new HashMap<String, Object>();
/** The JMXConnector's connection Id */
protected String connectionId = null;
/** The mbean server default domain */
protected String serverDomain = null;
/** The mbean server jvm instance runtime name */
protected String jvmName = null;
/** Flag to indicate if this Gmx is connected */
protected final AtomicBoolean connected = new AtomicBoolean(false);
/** Flag to indicate if this Gmx is has been remoted */
protected final AtomicBoolean remoted = new AtomicBoolean(false);
/** The instance MetaClass */
protected MetaClass metaClass;
/** The MetaMBean for the remoted class loader on this remote server */
protected MetaMBean remoteClassLoader = null;
/** The MetaMBean for the remoted MBeanServer on this remote server */
protected MetaMBean remotedMBeanServer = null;
/** The closure dehydrator */
protected final ClosureDehydrator dehydrator = new ClosureDehydrator();
/** A map of sets of registered JMX notification listeners */
protected final Map<ObjectName, Set<ObjectNameAwareListener>> registeredNotificationListeners = new ConcurrentHashMap<ObjectName, Set<ObjectNameAwareListener>>();
/** The platform MBeanServer Default Domain Name */
public static final String PLATFORM_DEFAULT_DOMAIN = ManagementFactory.getPlatformMBeanServer().getDefaultDomain();
/** The error message template for invalid arguments counts on listener adds */
public static final String INVALID_ARG_COUNT_TEMPLATE = "Invalid argument count. Closure expects [%s] but additional supplied closure argument count was [%s]. Diff can be 1 or 2 but was [%s]";
/** The standard JMX ObjectName prefix of the remotable MBeanServer MBean */
public static final String REMOTE_MBEANSERVER_ON_PREFIX = "org.helios.gmx:service=RemotableMBeanServer,domain=%s,host=%s,port=%s";
/** The standard JMX ObjectName prefix of the remotable MBeanServer MBean */
public static final String REMOTE_MLET_ON_PREFIX = "org.helios.gmx:service=ReverseClassLoader,host=%s,port=%s";
/** A set of Gmx references that will be closed and cleared on shutdown */
public static final Set<WeakReference<Gmx>> GMX_REFERENCES = new CopyOnWriteArraySet<WeakReference<Gmx>>();
/** This JVM's PID */
public static final String PID = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
static {
ByteCodeRepository.getInstance();
Runtime.getRuntime().addShutdownHook(new Thread(){
public void run() {
//System.out.println("Cleaning up Gmx References...");
int cnt = 0;
for(WeakReference<Gmx> ref: GMX_REFERENCES) {
Gmx gmx = ref.get();
if(gmx!=null) {
gmx.close();
cnt++;
}
}
GMX_REFERENCES.clear();
//System.out.println("Cleaned up " + cnt + " Gmx References");
}
});
}
public static void main(String[] args) {
try {
ReverseClassLoader.getInstance();
GroovyShell shell = new GroovyShell();
Script script = shell.parse(new File(System.getProperty("user.home") + File.separator + "GMXRemotingClosure.groovy"));
log("Script:" + script);
VirtualMachineBootstrap.getInstance();
for(VirtualMachineDescriptor vmd: VirtualMachine.list()) {
if(vmd.displayName().toLowerCase().contains("jconsole")) {
String pid = vmd.id();
log("Connecting to [" + pid + "]");
script.getBinding().setVariable("args", new String[]{pid});
//script.getBinding().setVariable("bytecode", ReverseClassLoader.getInstance().);
script.run();
}
}
} catch (Exception e) {
e.printStackTrace(System.err);
}
}
public static void log(Object msg) {
System.out.println(msg);
}
/**
* Creates a new Gmx
* @param mbeanServerConnection The wrapped MBeanServer connection
*/
private Gmx(MBeanServerConnection mbeanServerConnection) {
if(mbeanServerConnection==null) throw new IllegalArgumentException("The passed connection was null", new Throwable());
this.mbeanServerConnection = RuntimeMBeanServerConnection.getInstance(mbeanServerConnection);
if(this.mbeanServerConnection instanceof MBeanServer) {
this.mbeanServer = RuntimeMBeanServer.getInstance((MBeanServer)this.mbeanServerConnection);
connected.set(true);
} else {
this.mbeanServer = null;
}
serverDomain = this.mbeanServerConnection.getDefaultDomain();
try {
this.jvmName = (String)this.mbeanServerConnection.getAttribute(JMXHelper.objectName(ManagementFactory.RUNTIME_MXBEAN_NAME), "Name");
} catch (Exception e) {}
GMX_REFERENCES.add(new WeakReference<Gmx>(this));
}
/**
* Creates a new Gmx
* @param serviceURL The remote JMXServiceURL
*/
private Gmx(JMXServiceURL serviceURL, Map<String, ?> environment) {
if(serviceURL==null) throw new IllegalArgumentException("The passed serviceURL was null", new Throwable());
this.serviceURL = serviceURL;
try {
this.connector = JMXConnectorFactory.connect(serviceURL);
this.mbeanServerConnection = RuntimeMBeanServerConnection.getInstance(connector.getMBeanServerConnection());
this.connectionId = connector.getConnectionId();
connected.set(true);
connector.addConnectionNotificationListener(this, null, this.connectionId);
if(this.mbeanServerConnection instanceof MBeanServer) {
this.mbeanServer = RuntimeMBeanServer.getInstance((MBeanServer)this.mbeanServerConnection);
} else {
this.mbeanServer = null;
}
} catch (IOException e) {
throw new RuntimeException("Failed to connect to remote MBeanServer on URL [" + serviceURL + "]");
}
serverDomain = this.mbeanServerConnection.getDefaultDomain();
try {
this.jvmName = (String)this.mbeanServerConnection.getAttribute(JMXHelper.objectName(ManagementFactory.RUNTIME_MXBEAN_NAME), "Name");
} catch (Exception e) {}
GMX_REFERENCES.add(new WeakReference<Gmx>(this));
}
/**
* Creates a new Gmx instance that wraps the local platform MBeanServer
* @return a new local platform MBeanServer wrapping Gmx.
*/
public static Gmx newInstance() {
return new Gmx(ManagementFactory.getPlatformMBeanServer());
}
/**
* Creates a new Gmx instance that wraps the passed MBeanServer
* @param server The MBeanServer to wrap
* @return a new MBeanServer wrapping Gmx.
*/
public static Gmx newInstance(MBeanServer server) {
return new Gmx(server);
}
/**
* Creates a new Gmx instance that wraps a local MBeanServer
* @param defaultDomain The default domain name to get the MBeanServer for
* @return a new local MBeanServer wrapping Gmx.
*/
public static Gmx newInstance(String defaultDomain) {
if(defaultDomain==null || PLATFORM_DEFAULT_DOMAIN.equals(defaultDomain)) {
return new Gmx(ManagementFactory.getPlatformMBeanServer());
}
for(MBeanServer server: MBeanServerFactory.findMBeanServer(null)) {
if(server.getDefaultDomain().equals(defaultDomain)) {
return new Gmx(server);
}
}
throw new IllegalArgumentException("No MBeanServer found for default domain name [" + defaultDomain + "]", new Throwable());
}
/**
* Uses the <a ref="http://docs.oracle.com/javase/6/docs/jdk/api/attach/spec/index.html">VM Attach API</a> to connect to a local VM and acquire an MBeanServerConnection.
* @param vmId The target virtual machine identifier, or, usually, the JVM's process id.
* @return a remote type Gmx to a local JVM outside this VM.
*/
public static Gmx attachInstance(String vmId) {
VirtualMachineBootstrap.getInstance();
VirtualMachine vm = VirtualMachine.attach(vmId);
return new Gmx(vm.getJMXServiceURL(), new HashMap<String, Object>(0));
}
/**
* Creates a new remote Gmx
* @param serviceURL The JMXServiceURL to create the Gmx from
* @return a remote Gmx
*/
public static Gmx remote(JMXServiceURL serviceURL) {
ReverseClassLoader.getInstance();
return new Gmx(serviceURL, new HashMap<String, Object>(0));
}
/**
* Creates a new remote Gmx
* @param serviceURL The JMXServiceURL to create the Gmx from
* @return a remote Gmx
*/
public static Gmx remote(CharSequence serviceURL) {
return new Gmx(JMXHelper.serviceURL(serviceURL), new HashMap<String, Object>(0));
}
/**
* Locates all JVMs on the local host using the attach API and invokes the passed closure on each.
* If a closure is provided, the Gmx will be closed after the closure is invoked and the return value will be null.
* Otherwise, the return value will be live Gmx instances.
* Attach failures are silently ignored.
* @param includeThis If true, includes this JVM (the one running this command)
* @param gmxHandler The optional closure to execute on each Gmx created for each discovered JVM.
* @return An array of Gmx instances created for each located and successfully attached JVM.
*/
public static Gmx[] attachInstances(boolean includeThis, Closure<Gmx> gmxHandler) {
Set<Gmx> set = new HashSet<Gmx>();
for(VirtualMachineDescriptor vmd: VirtualMachine.list()) {
if(!vmd.id().equals(PID) || (vmd.id().equals(PID) && includeThis)) {
Gmx gmx = null;
try {
gmx = Gmx.attachInstance(vmd.id());
} catch (Exception e) {
continue;
}
if(gmxHandler!=null) {
try {
gmxHandler.call(gmx);
} finally {
gmx.close();
}
} else {
try {
set.add(Gmx.attachInstance(vmd.id()));
} catch (Exception e) {}
}
}
}
return gmxHandler!=null ? null : set.toArray(new Gmx[set.size()]);
}
/**
* Locates all JVMs on the local host (not including this one) using the attach API and invokes the passed closure on each.
* Attach failures are silently ignored.
* If a closure is provided, the Gmx will be closed after the closure is invoked and the return value will be null.
* Otherwise, the return value will be live Gmx instances.
* @param gmxHandler The optional closure to execute on each Gmx created for each discovered JVM.
* @return An array of Gmx instances created for each located and successfully attached JVM.
*/
public static Gmx[] attachInstances(Closure<Gmx> gmxHandler) {
return attachInstances(false, gmxHandler);
}
/**
* Locates all JVMs on the local host (not including this one) using the attach API and returns a Gmx for each.
* Attach failures are silently ignored.
* @return An array of Gmx instances created for each located and successfully attached JVM.
*/
public static Gmx[] attachInstances() {
return attachInstances(false, null);
}
/**
* Locates all JVMs on the local host using the attach API and returns a Gmx for each.
* Attach failures are silently ignored.
* @param includeThis If true, includes this JVM (the one running this command)
* @return An array of Gmx instances created for each located and successfully attached JVM.
*/
public static Gmx[] attachInstances(boolean includeThis) {
return attachInstances(includeThis, null);
}
/**
* Determines if this Gmx is remote
* @return true if this Gmx is remote, false otherwise.
*/
public boolean isRemote() {
return connector!=null;
}
/**
* Closes a remote connection.
* If this is not a remote Gmx, or the connection is already closed, the command does nothing.
*/
public void close() {
for(Map.Entry<ObjectName, Set<ObjectNameAwareListener>> entry: registeredNotificationListeners.entrySet()) {
for(ObjectNameAwareListener listener: entry.getValue()) {
try {
removeNotificationListener(listener.getObjectName(), listener);
} catch (Exception e) {}
}
entry.getValue().clear();
}
registeredNotificationListeners.clear();
if(remoteClassLoader!=null) {
try {
mbeanServerConnection.unregisterMBean(remoteClassLoader.getObjectName());
remoteClassLoader=null;
} catch (Exception e) {}
}
if(remotedMBeanServer!=null) {
try {
mbeanServerConnection.unregisterMBean(remotedMBeanServer.getObjectName());
remotedMBeanServer=null;
} catch (Exception e) {}
}
if(connector!=null) {
try { connector.close(); } catch (Exception e) {}
connected.set(false);
}
}
/**
* Indicates if this Gmx is connected.
* @return true if this Gmx is live, false if it is disconnected.
*/
public boolean isConnected() {
return connected.get();
}
/**
* {@inheritDoc}
* @see java.lang.Object#finalize()
*/
@Override
protected void finalize() throws Throwable {
close();
super.finalize();
}
// =========================================================================================
// Remoting operations
// =========================================================================================
/**
* Installs the remotable MBeanServer on the target MBeanServer
* @param privateMlet If true, the MLet will be private, otherwise it will be public.
* @return this Gmx
*/
public Gmx installRemote(boolean privateMlet) {
ReverseClassLoader.getInstance().installRemotableMBeanServer(this, privateMlet);
remoted.set(true);
return this;
}
/**
* Installs the remotable MBeanServer on the target MBeanServer with a private MLet
* @return this Gmx
*/
public Gmx installRemote() {
return installRemote(true);
}
/**
* Callback from the reverse class loader providing the ObjectNames of the insalled remotes.
* @param remoteClassLoaderOn The JMX ObjectName of the remote classloader
* @param remoteMBeanServerOn The JMX ObjectName of the remote MBeanServer
*/
public void installedRemote(ObjectName remoteClassLoaderOn, ObjectName remoteMBeanServerOn) {
remoteClassLoader = mbean(remoteClassLoaderOn);
remotedMBeanServer = mbean(remoteMBeanServerOn);
final MetaMBean finalBean = this.remotedMBeanServer;
this.mbeanServer = RuntimeMBeanServer.getInstance(
(MBeanServer) Proxy.newProxyInstance(remotedMBeanServer.getClass().getClassLoader(), new Class<?>[]{MBeanServer.class}, new InvocationHandler(){
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return finalBean.invokeMethod(method.getName(), args);
}
}));
System.out.println("Created remoted MBeanServer proxy [" + this.mbeanServer + "]");
}
/**
* Returns the remotable MBeanServer MetaMBean for this Gmx.
* @return a MetaMBean
*/
public MetaMBean gmxRemote() {
if(!isRemoted()) {
installRemote();
}
return remotedMBeanServer;
}
/**
* Invokes the passed closure, passing the {@link MBeanServerConnection} as the <code>it</code>.
* If the Gmx is remote, the closure will be serialized and executed on the foreign {@link MBeanServer}.
* @param <T> The expetced return type of the closure
* @param closure The closure to execute
* @param args Optional arguments to bind to the closure as an <code>args</code> property.
* @return The return value of the closure
*/
public <T> T exec(Closure<T> closure, Object...args) {
if(closure==null) throw new IllegalArgumentException("The passed closure was null", new Throwable());
if(!isRemote()) {
return closure.call(mergeArguments(this, args));
}
if(!isRemoted()) {
synchronized(remoted) {
if(!isRemoted()) {
installRemote();
}
}
}
return invokeRemoteClosure(closure, args);
}
/**
* Executes the passed closure forcing local execution.
* If the Gmx represents a local {@link MBeanServer}, this call is the same as {@link Gmx#exec(Closure)}.
* If the Gmx represents a remote {@link MBeanServerConnection}, this method supresses remoting and invokes the call against the remote {@link MBeanServerConnection} stub.
* @param closure The closure to be executed with this Gmx's {@link MBeanServerConnection} as the first parameter.
* @param args The caller supplied arguments to the closure
* @return the return value of the closure
*/
public <T> T execLocal(Closure<T> closure, Object...args) {
if(!isRemote()) {
return exec(closure, args);
}
return closure.call(mergeArguments(args, this));
}
/**
* Invokes a closure remotely in the foreign MBeanServer
* @param closure The closure to execute
* @param arguments The closure arguments
* @return The return value of the closure execution.
*/
@SuppressWarnings("unchecked")
public <T> T invokeRemoteClosure(Closure<T> closure, Object...arguments) {
if(closure==null) throw new IllegalArgumentException("The passed closure was null", new Throwable());
dehydrator.dehydrate(closure);
return (T)remotedMBeanServer.invokeMethod("invokeClosure", new Object[]{closure, arguments});
}
/**
* Registers a notification listener with the MBeanServer
* @param objectName The JMX ObjectName that represents the MBeans from which to receive notifications
* @param listener A closure that will passed the notification and handback.
* @param filter A closure that will be passed the notification to determine if it should be filtered or not. If null, no filtering will be performed before handling notifications.
* @param handback The object to be passed back to the listener closure. Can be null (so long as the notification is not expecting it....)
* @param closureArgs Optional arguments to the listener closure
* @return The wrapped listener that can be used to unregister the listener
*/
public ObjectNameAwareListener addListener(CharSequence objectName, Closure<Void> listener, Closure<Boolean> filter, Object handback, Object...closureArgs ) {
return addListener(JMXHelper.objectName(objectName), listener, filter, handback, closureArgs);
}
/**
* Registers a notification listener with the MBeanServer
* @param objectName The JMX ObjectName that represents the MBeans from which to receive notifications
* @param listener A closure that will passed the notification and handback.
* @param filter A closure that will be passed the notification to determine if it should be filtered or not. If null, no filtering will be performed before handling notifications.
* @param handback The object to be passed back to the listener closure. Can be null (so long as the notification is not expecting it....)
* @param closureArgs Optional arguments to the listener closure
* @return The wrapped listener that can be used to unregister the listener
*/
public ObjectNameAwareListener addListener(ObjectName objectName, Closure<Void> listener, Closure<Boolean> filter, Object handback, Object...closureArgs ) {
if(isRemote()) {
if(!isRemoted()) {
installRemote();
}
}
int expectedArgCount = listener.getParameterTypes().length;
int clozureSuppliedArgCount = closureArgs==null ? 0 : closureArgs.length;
int notificationSuppliedArgCount = expectedArgCount-clozureSuppliedArgCount;
if(notificationSuppliedArgCount <1 || notificationSuppliedArgCount >2) {
throw new IllegalArgumentException(String.format(INVALID_ARG_COUNT_TEMPLATE, expectedArgCount, clozureSuppliedArgCount, notificationSuppliedArgCount));
}
boolean expectsHandback = notificationSuppliedArgCount==2;
ObjectNameAwareListener onAwareListener = new ClosureWrappingNotificationListener(expectsHandback, objectName, dehydrator.dehydrate(listener), closureArgs);
_addRegisteredListener(onAwareListener);
mbeanServerConnection.addNotificationListener(objectName, onAwareListener, new ClosureWrappingNotificationFilter(dehydrator.dehydrate(filter)), handback);
return onAwareListener;
}
/**
* Registers a JMX {@link NotificationListener} with the {@link MBeanServer}
* @param objectName The JMX {@link ObjectName} that represents the MBeans from which to receive notifications
* @param listener A closure that will passed the notification and handback.
* @param closureArgs Optional arguments to the listener closure
* @return The wrapped listener that can be used to unregister the listener
*/
public ObjectNameAwareListener addListener(CharSequence objectName, Closure<Void> listener, Object...closureArgs) {
return addListener(objectName, listener, null, null, closureArgs);
}
/**
* Registers a JMX {@link NotificationListener} with the {@link MBeanServer}
* @param objectName The JMX {@link ObjectName} that represents the MBeans from which to receive notifications
* @param listener A closure that will passed the notification and handback.
* @param closureArgs Optional arguments to the listener closure
* @return The wrapped listener that can be used to unregister the listener
*/
public ObjectNameAwareListener addListener(ObjectName objectName, Closure<Void> listener, Object...closureArgs) {
return addListener(objectName, listener, null, null, closureArgs);
}
/**
* Removes a registered listener
* @param name The ObjectName of the MBean where the listener was registered
* @param listener The listener to remove
*/
public void removeListener(ObjectName name, NotificationListener listener) {
mbeanServerConnection.removeNotificationListener(name, listener);
_removeRegisteredListener(name, listener);
}
/**
* Removes a registered listener
* @param listener The object name aware listener to remove
*/
public void removeListener(ObjectNameAwareListener listener) {
mbeanServerConnection.removeNotificationListener(listener.getObjectName(), listener);
_removeRegisteredListener(listener.getObjectName(), listener);
}
/**
* Stores a registered JMX {@link NotificationListener} keyed by {@link ObjectName}.
* @param listener The {@link NotificationListener} that was registered
*/
protected void _addRegisteredListener(ObjectNameAwareListener listener) {
Set<ObjectNameAwareListener> listeners = registeredNotificationListeners.get(listener.getObjectName());
if(listeners==null) {
synchronized(registeredNotificationListeners) {
listeners = registeredNotificationListeners.get(listener.getObjectName());
if(listeners==null) {
listeners = new CopyOnWriteArraySet<ObjectNameAwareListener>();
registeredNotificationListeners.put(listener.getObjectName(), listeners);
}
}
}
listeners.add(listener);
}
/**
* Removes a registered JMX {@link NotificationListener} keyed by {@link ObjectName}.
* @param objectName The {@link ObjectName} of the MBean to remove the listener from
* @param listener The {@link NotificationListener} that was registered
*/
protected void _removeRegisteredListener(ObjectName objectName, NotificationListener listener) {
Set<ObjectNameAwareListener> listeners = registeredNotificationListeners.get(objectName);
if(listeners!=null) {
synchronized(listeners) {
listeners.remove(listener);
if(listeners.isEmpty()) {
listeners.remove(objectName);
}
}
}
}
// =========================================================================================
// MetaMBean operations
// =========================================================================================
/**
* Queries the MBeanServer for MBeans with matching ObjectNames and executes the passed closure on each.
* @param objectName The ObjectName to match against
* @param beanHandler A closure which will operate on each returned {@link MetaMBean}
* @return An array of matching {@link MetaMBean}s.
*/
public MetaMBean[] mbeans(ObjectName objectName, Closure<MetaMBean> beanHandler) {
Set<MetaMBean> metaBeans = new HashSet<MetaMBean>();
for(ObjectName on: mbeanServerConnection.queryNames(objectName, null)) {
MetaMBean bean = MetaMBean.newInstance(on, this);
metaBeans.add(bean);
if(beanHandler!=null) beanHandler.call(bean);
}
return metaBeans.toArray(new MetaMBean[metaBeans.size()]);
}
/**
* Queries the MBeanServer for MBeans with matching ObjectNames and executes the passed closure on the first instance found.
* @param objectName The ObjectName to match against
* @param beanHandler A closure which will operate on the first returned {@link MetaMBean}
* @return The first matched {@link MetaMBean} or null if there was no match.
*/
public MetaMBean mbean(ObjectName objectName, Closure<MetaMBean> beanHandler) {
Set<ObjectName> matches = mbeanServerConnection.queryNames(objectName, null);
if(matches.isEmpty()) return null;
MetaMBean bean = MetaMBean.newInstance(matches.iterator().next(), this);
if(beanHandler!=null) beanHandler.call(bean);
return bean;
}
/**
* Queries the MBeanServer for MBeans with matching ObjectNames and executes the passed closure on the first instance found.
* @param objectName The ObjectName to match against
* @param beanHandler A closure which will operate on the first returned {@link MetaMBean}
* @return The first matched {@link MetaMBean} or null if there was no match.
*/
public MetaMBean mbean(CharSequence objectName, Closure<MetaMBean> beanHandler) {
return mbean(JMXHelper.objectName(objectName), beanHandler);
}
/**
* Queries the MBeanServer for MBeans with matching ObjectNames and returns the first instance found.
* @param objectName The ObjectName to match against
* @return The first matched {@link MetaMBean} or null if there was no match.
*/
public MetaMBean mbean(CharSequence objectName) {
return mbean(JMXHelper.objectName(objectName), null);
}
/**
* Queries the MBeanServer for MBeans with matching ObjectNames and returns the first instance found.
* @param objectName The ObjectName to match against
* @return The first matched {@link MetaMBean} or null if there was no match.
*/
public MetaMBean mbean(ObjectName objectName) {
Set<ObjectName> matches = mbeanServerConnection.queryNames(objectName, null);
if(matches.isEmpty()) return null;
return MetaMBean.newInstance(matches.iterator().next(), this);
}
/**
* Queries the MBeanServer for MBeans with matching ObjectNames
* @param objectName The ObjectName to match against
* @return An array of matching {@link MetaMBean}s.
*/
public MetaMBean[] mbeans(ObjectName objectName) {
return mbeans(objectName, null);
}
/**
* Queries the MBeanServer for MBeans with matching ObjectNames and executes the passed closure on each.
* @param objectName The ObjectName to match against
* @param beanHandler A closure which will operate on each returned {@link MetaMBean}
* @return An array of matching {@link MetaMBean}s.
*/
public MetaMBean[] mbeans(CharSequence objectName, Closure<MetaMBean> beanHandler) {
return mbeans(JMXHelper.objectName(objectName), beanHandler);
}
/**
* Queries the MBeanServer for MBeans with matching ObjectNames
* @param objectName The ObjectName to match against
* @return An array of matching {@link MetaMBean}s.
*/
public MetaMBean[] mbeans(CharSequence objectName) {
return mbeans(JMXHelper.objectName(objectName), null);
}
// =========================================================================================
// GroovyObject implementation
// =========================================================================================
/**
* {@inheritDoc}
* @see groovy.lang.GroovyObject#getMetaClass()
*/
public MetaClass getMetaClass() {
if (metaClass == null) {
metaClass = GroovySystem.getMetaClassRegistry().getMetaClass(Gmx.class);
}
return metaClass;
}
/**
* {@inheritDoc}
* @see groovy.lang.GroovyObject#getProperty(java.lang.String)
*/
@Override
public Object getProperty(String propertyName) {
return getMetaClass().getProperty(this, propertyName);
}
/**
* {@inheritDoc}
* @see groovy.lang.GroovyObject#invokeMethod(java.lang.String, java.lang.Object)
*/
@Override
public Object invokeMethod(String name, Object args) {
return getMetaClass().invokeMethod(this, name, args);
}
/**
* {@inheritDoc}
* @see groovy.lang.GroovyObject#setMetaClass(groovy.lang.MetaClass)
*/
@Override
public void setMetaClass(MetaClass metaClass) {
this.metaClass = metaClass;
}
/**
* {@inheritDoc}
* @see groovy.lang.GroovyObject#setProperty(java.lang.String, java.lang.Object)
*/
@Override
public void setProperty(String propertyName, Object newValue) {
getMetaClass().setProperty(this, propertyName, newValue);
}
// =========================================================================================
// MBeanServerConnection implementation
// =========================================================================================
/**
* {@inheritDoc}
* @see javax.management.MBeanServerConnection#addNotificationListener(javax.management.ObjectName, javax.management.NotificationListener, javax.management.NotificationFilter, java.lang.Object)
*/
public void addNotificationListener(ObjectName name,
NotificationListener listener, NotificationFilter filter,
Object handback) {
mbeanServerConnection.addNotificationListener(name, listener, filter,
handback);
}
/**
* {@inheritDoc}
* @see javax.management.MBeanServerConnection#addNotificationListener(javax.management.ObjectName, javax.management.ObjectName, javax.management.NotificationFilter, java.lang.Object)
*/
public void addNotificationListener(ObjectName name, ObjectName listener,
NotificationFilter filter, Object handback)
throws InstanceNotFoundException, IOException {
mbeanServerConnection.addNotificationListener(name, listener, filter,
handback);
}
/**
* {@inheritDoc}
* @see javax.management.MBeanServerConnection#createMBean(java.lang.String, javax.management.ObjectName, java.lang.Object[], java.lang.String[])
*/
public ObjectInstance createMBean(String className, ObjectName name,
Object[] params, String[] signature) throws ReflectionException,
InstanceAlreadyExistsException, MBeanRegistrationException,
MBeanException, NotCompliantMBeanException, IOException {
return mbeanServerConnection.createMBean(className, name, params,
signature);
}
/**
* {@inheritDoc}
* @see javax.management.MBeanServerConnection#createMBean(java.lang.String, javax.management.ObjectName, javax.management.ObjectName, java.lang.Object[], java.lang.String[])
*/
public ObjectInstance createMBean(String className, ObjectName name,
ObjectName loaderName, Object[] params, String[] signature)
throws ReflectionException, InstanceAlreadyExistsException,
MBeanRegistrationException, MBeanException,
NotCompliantMBeanException, InstanceNotFoundException, IOException {
return mbeanServerConnection.createMBean(className, name, loaderName,
params, signature);
}
/**
* {@inheritDoc}
* @see javax.management.MBeanServerConnection#createMBean(java.lang.String, javax.management.ObjectName, javax.management.ObjectName)
*/
public ObjectInstance createMBean(String className, ObjectName name,
ObjectName loaderName) throws ReflectionException,
InstanceAlreadyExistsException, MBeanRegistrationException,
MBeanException, NotCompliantMBeanException,
InstanceNotFoundException, IOException {
return mbeanServerConnection.createMBean(className, name, loaderName);
}
/**
* {@inheritDoc}
* @see javax.management.MBeanServerConnection#createMBean(java.lang.String, javax.management.ObjectName)
*/
public ObjectInstance createMBean(String className, ObjectName name)
throws ReflectionException, InstanceAlreadyExistsException,
MBeanRegistrationException, MBeanException,
NotCompliantMBeanException, IOException {
return mbeanServerConnection.createMBean(className, name);
}
/**
* {@inheritDoc}
* @see javax.management.MBeanServerConnection#getAttribute(javax.management.ObjectName, java.lang.String)
*/
public Object getAttribute(ObjectName name, String attribute)
throws MBeanException, AttributeNotFoundException,
InstanceNotFoundException, ReflectionException, IOException {
return mbeanServerConnection.getAttribute(name, attribute);
}
/**
* {@inheritDoc}
* @see javax.management.MBeanServerConnection#getAttributes(javax.management.ObjectName, java.lang.String[])
*/
public AttributeList getAttributes(ObjectName name, String[] attributes)
throws InstanceNotFoundException, ReflectionException, IOException {
return mbeanServerConnection.getAttributes(name, attributes);
}
/**
* {@inheritDoc}
* @see javax.management.MBeanServerConnection#getDefaultDomain()
*/
public String getDefaultDomain() throws IOException {
return mbeanServerConnection.getDefaultDomain();
}
/**
* {@inheritDoc}
* @see javax.management.MBeanServerConnection#getDomains()
*/
public String[] getDomains() throws IOException {
return mbeanServerConnection.getDomains();
}
/**
* {@inheritDoc}
* @see javax.management.MBeanServerConnection#getMBeanCount()
*/
public Integer getMBeanCount() throws IOException {
return mbeanServerConnection.getMBeanCount();
}
/**
* {@inheritDoc}
* @see javax.management.MBeanServerConnection#getMBeanInfo(javax.management.ObjectName)
*/
public MBeanInfo getMBeanInfo(ObjectName name)
throws InstanceNotFoundException, IntrospectionException,
ReflectionException, IOException {
return mbeanServerConnection.getMBeanInfo(name);
}
/**
* {@inheritDoc}
* @see javax.management.MBeanServerConnection#getObjectInstance(javax.management.ObjectName)
*/
public ObjectInstance getObjectInstance(ObjectName name)
throws InstanceNotFoundException, IOException {
return mbeanServerConnection.getObjectInstance(name);
}
/**
* {@inheritDoc}
* @see javax.management.MBeanServerConnection#invoke(javax.management.ObjectName, java.lang.String, java.lang.Object[], java.lang.String[])
*/
public Object invoke(ObjectName name, String operationName,
Object[] params, String[] signature)
throws InstanceNotFoundException, MBeanException,
ReflectionException, IOException {
return mbeanServerConnection.invoke(name, operationName, params,
signature);
}
/**
* {@inheritDoc}
* @see javax.management.MBeanServerConnection#isInstanceOf(javax.management.ObjectName, java.lang.String)
*/
public boolean isInstanceOf(ObjectName name, String className)
throws InstanceNotFoundException, IOException {
return mbeanServerConnection.isInstanceOf(name, className);
}
/**
* {@inheritDoc}
* @see javax.management.MBeanServerConnection#isRegistered(javax.management.ObjectName)
*/
public boolean isRegistered(ObjectName name) throws IOException {
return mbeanServerConnection.isRegistered(name);
}
/**
* {@inheritDoc}
* @see javax.management.MBeanServerConnection#queryMBeans(javax.management.ObjectName, javax.management.QueryExp)
*/
public Set<ObjectInstance> queryMBeans(ObjectName name, QueryExp query)
throws IOException {
return mbeanServerConnection.queryMBeans(name, query);
}
/**
* {@inheritDoc}
* @see javax.management.MBeanServerConnection#queryNames(javax.management.ObjectName, javax.management.QueryExp)
*/
public Set<ObjectName> queryNames(ObjectName name, QueryExp query)
throws IOException {
return mbeanServerConnection.queryNames(name, query);
}
/**
* {@inheritDoc}
* @see javax.management.MBeanServerConnection#removeNotificationListener(javax.management.ObjectName, javax.management.NotificationListener, javax.management.NotificationFilter, java.lang.Object)
*/
public void removeNotificationListener(ObjectName name,
NotificationListener listener, NotificationFilter filter,
Object handback) throws InstanceNotFoundException,
ListenerNotFoundException, IOException {
mbeanServerConnection.removeNotificationListener(name, listener,
filter, handback);
}
/**
* {@inheritDoc}
* @see javax.management.MBeanServerConnection#removeNotificationListener(javax.management.ObjectName, javax.management.NotificationListener)
*/
public void removeNotificationListener(ObjectName name,
NotificationListener listener) throws InstanceNotFoundException,
ListenerNotFoundException, IOException {
mbeanServerConnection.removeNotificationListener(name, listener);
}
/**
* {@inheritDoc}
* @see javax.management.MBeanServerConnection#removeNotificationListener(javax.management.ObjectName, javax.management.ObjectName, javax.management.NotificationFilter, java.lang.Object)
*/
public void removeNotificationListener(ObjectName name,
ObjectName listener, NotificationFilter filter, Object handback)
throws InstanceNotFoundException, ListenerNotFoundException,
IOException {
mbeanServerConnection.removeNotificationListener(name, listener,
filter, handback);
}
/**
* {@inheritDoc}
* @see javax.management.MBeanServerConnection#removeNotificationListener(javax.management.ObjectName, javax.management.ObjectName)
*/
public void removeNotificationListener(ObjectName name, ObjectName listener)
throws InstanceNotFoundException, ListenerNotFoundException,
IOException {
mbeanServerConnection.removeNotificationListener(name, listener);
}
/**
* {@inheritDoc}
* @see javax.management.MBeanServerConnection#setAttribute(javax.management.ObjectName, javax.management.Attribute)
*/
public void setAttribute(ObjectName name, Attribute attribute)
throws InstanceNotFoundException, AttributeNotFoundException,
InvalidAttributeValueException, MBeanException,
ReflectionException, IOException {
mbeanServerConnection.setAttribute(name, attribute);
}
/**
* {@inheritDoc}
* @see javax.management.MBeanServerConnection#setAttributes(javax.management.ObjectName, javax.management.AttributeList)
*/
public AttributeList setAttributes(ObjectName name, AttributeList attributes)
throws InstanceNotFoundException, ReflectionException, IOException {
return mbeanServerConnection.setAttributes(name, attributes);
}
/**
* {@inheritDoc}
* @see javax.management.MBeanServerConnection#unregisterMBean(javax.management.ObjectName)
*/
public void unregisterMBean(ObjectName name)
throws InstanceNotFoundException, MBeanRegistrationException,
IOException {
mbeanServerConnection.unregisterMBean(name);
}
// =========================================================================================
// NotificationListener implementation
// =========================================================================================
/**
* Callback when this connection is opened
* @param connNot The connection notification
*/
public void onConnectionOpened(JMXConnectionNotification connNot) {
//System.out.println("Connection Opened:" + connNot);
}
/**
* Callback when this connection is closed
* @param connNot The connection notification
*/
public void onConnectionClosed(JMXConnectionNotification connNot) {
//System.out.println("Connection Closed:" + connNot);
//close();
}
/**
* Callback when this connection fails
* @param connNot The connection notification
*/
public void onConnectionFailed(JMXConnectionNotification connNot) {
System.out.println("Connection Failed:" + connNot);
//close();
}
/**
* Callback when this connection may have lost notifications
* @param connNot The connection notification
*/
public void onConnectionLostNotifications(JMXConnectionNotification connNot) {
System.out.println("Connection Lost Notifications:" + connNot);
}
/**
* {@inheritDoc}
* <p>Delegates notifications:<ul>
* <li>{@link JMXConnectionNotification}s: Delegated locally to Gmx</li>
* </ul>
* @see javax.management.NotificationListener#handleNotification(javax.management.Notification, java.lang.Object)
*/
public void handleNotification(Notification notification, Object handback) {
if(notification instanceof JMXConnectionNotification) {
JMXConnectionNotification connNot = (JMXConnectionNotification)notification;
String type = connNot.getType();
if(JMXConnectionNotification.CLOSED.equals(type)) {
onConnectionClosed(connNot);
} else if(JMXConnectionNotification.FAILED.equals(type)) {
onConnectionFailed(connNot);
} else if(JMXConnectionNotification.OPENED.equals(type)) {
onConnectionOpened(connNot);
} else if(JMXConnectionNotification.NOTIFS_LOST.equals(type)) {
onConnectionLostNotifications(connNot);
}
}
}
/**
* Returns the internal MBeanServer reference
* @return the internal MBeanServer reference which may be null if this is a remote
* connection and has not been server remoted.
*/
public RuntimeMBeanServer getMBeanServer() {
return mbeanServer;
}
/**
* Returns the internal MBeanServerConnection reference
* @return the internal MBeanServerConnection reference
*/
public RuntimeMBeanServerConnection getMBeanServerConnection() {
return mbeanServerConnection;
}
/**
* The remote JMX connection
* @return the remote JMX connection which may be null if this is not a remote connection
*/
public JMXConnector getConnector() {
return connector;
}
/**
* The remote JMX connection JMX Service URL
* @return the remote JMX connection JMX Service URL which may be null if this is not a remote connection
*/
public JMXServiceURL getServiceURL() {
return serviceURL;
}
/**
* Returns the configured environment for this connection
* @return the configured environment for this connection which may be empty
* because no environment was set or because this is not a remote connection.
*/
public Map<String, ?> getEnvironment() {
return environment;
}
/**
* Returns the remote connection Id
* @return the remote connection Id which may be null if this is not a remote connection
*/
public String getConnectionId() {
return connectionId;
}
/**
* Returns the MBeanServer default domain.
* This value is cached so this is a more efficient call than {@link Gmx#getDefaultDomain()}.
* @return the MBeanServer default domain.
*/
public String getServerDomain() {
return serverDomain;
}
/**
* Returns the JVM Runtime name for the connected JVM
* If the connected MBeanServer does not have the {@link java.lang.management.RuntimeMXBean} registered, this will be null.
* @return the JVM Runtime name for the connected JVM
*/
public String getJvmName() {
return jvmName;
}
/**
* Constructs a <code>String</code> with key attributes in name = value format.
* @return a <code>String</code> representation of this object.
*/
@Override
public String toString() {
final String TAB = "\n\t";
StringBuilder retValue = new StringBuilder("Gmx [")
.append(TAB).append("DefaultDomain = ").append(this.serverDomain)
.append(TAB).append("JvmName = ").append(this.jvmName)
.append(TAB).append("Remote = ").append(this.isRemote());
if(isRemote()) {
retValue.append(TAB).append("serviceURL = ").append(this.serviceURL)
.append(TAB).append("connectionId = ").append(this.connectionId);
}
retValue.append("\n]");
return retValue.toString();
}
/**
* Callback from the ReverseClassLoader when the remotes have been installed
* @param remoteClassLoader the MetaMBean for the remoted class loader on this remote server
* @param remotedMBeanServer the MetaMBean for the remoted MBeanServer on this remote server
*/
protected void installedRemotes(MetaMBean remoteClassLoader, MetaMBean remotedMBeanServer) {
try {
this.remoteClassLoader = remoteClassLoader;
this.remotedMBeanServer = remotedMBeanServer;
final MetaMBean finalBean = this.remotedMBeanServer;
this.mbeanServer = (RuntimeMBeanServer) Proxy.newProxyInstance(remotedMBeanServer.getClass().getClassLoader(), new Class<?>[]{RuntimeMBeanServer.class}, new InvocationHandler(){
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return finalBean.invokeMethod(method.getName(), args);
}
});
System.out.println("Created remoted MBeanServer proxy [" + this.mbeanServer + "]");
} catch (Exception e) {
throw new RuntimeException("Failed to handle callback on remote install", e);
}
}
/**
* Returns the MetaMBean for the remoted class loader on this remote server
* May be null if this has not been installed on the remote
* @return the remoteClassLoader MetaMBean
*/
public MetaMBean getRemoteClassLoader() {
return remoteClassLoader;
}
/**
* Returns the MetaMBean for the remoted MBeanServer on this remote server
* May be null if this has not been installed on the remote
* @return the remotedMBeanServer MetaMBean
*/
public MetaMBean getRemotedMBeanServer() {
return remotedMBeanServer;
}
/**
* Flag to indicate if this Gmx is has been remoted
* @return true if the Gmx is remoted, false otherwise.
*/
public boolean isRemoted() {
return remoted.get();
}
/**
* Merges internally supplied arguments to a closure with caller supplied arguments into one flat object array.
* @param suppliedArgs The caller supplied arguments
* @param injectedArgs The internal API supplied arguments
* @return an Object array
*/
public static Object[] mergeArguments(Object suppliedArgs, Object...injectedArgs) {
Object[] flattened = null;
List<Object> fobjs = new ArrayList<Object>();
if(injectedArgs!=null && injectedArgs.length>0) {
Collections.addAll(fobjs, injectedArgs);
}
if(suppliedArgs!=null) {
if(Object[].class.isAssignableFrom(suppliedArgs.getClass())) {
Collections.addAll(fobjs, (Object[])suppliedArgs);
} else {
fobjs.add(suppliedArgs);
}
}
flattened = new Object[fobjs.size()];
return fobjs.toArray(flattened);
}
}