/** * 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.Binding; 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.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.atomic.AtomicReference; import javax.management.Attribute; import javax.management.MBeanAttributeInfo; import javax.management.MBeanInfo; import javax.management.MBeanOperationInfo; import javax.management.MBeanParameterInfo; import javax.management.ObjectName; import org.helios.gmx.jmx.ObjectNameAwareListener; import org.helios.gmx.util.JMXHelper; import org.helios.gmx.util.Primitive; /** * <p>Title: MetaMBean</p> * <p>Description: A meta object that represents an MBean.</p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.gmx.MetaMBean</code></p> */ public class MetaMBean implements GroovyObject { /** The JMX ObjectName of the MBean */ protected final ObjectName objectName; /** The Gmx reference that created this MetaMBean */ protected final Gmx gmx; /** A reference to the MBean's MBeanInfo */ protected final AtomicReference<MBeanInfo> mbeanInfo = new AtomicReference<MBeanInfo>(null); /** A set of attribute names */ protected final Set<String> attributeNames = new CopyOnWriteArraySet<String>(); /** A map of operations keyed by operation name with a set of all signatures for that name as the value */ protected final Map<String, TreeSet<OperationSignature>> operations = new HashMap<String, TreeSet<OperationSignature>>(); /** The instance MetaClass */ protected MetaClass metaClass; /** The dynamically generated script text used to invoke mbean operations */ protected final AtomicReference<String> invokerScriptText = new AtomicReference<String>(null); /** The dynamically generated script used to invoke mbean operations */ protected Script invokerScript = null; /** * Creates a new MetaMBean * @param objectName The JMX ObjectName * @param gmx The Gmx instance that created this MetaMBean * @return a MetaMBean */ public static MetaMBean newInstance(ObjectName objectName, Gmx gmx) { if(objectName==null) throw new IllegalArgumentException("The passed ObjectName was null", new Throwable()); return new MetaMBean(objectName, gmx); } /** * Creates a new MetaMBean * @param objectName The JMX ObjectName * @param gmx The Gmx instance that created this MetaMBean * @return a MetaMBean */ public static MetaMBean newInstance(CharSequence objectName, Gmx gmx) { return newInstance(JMXHelper.objectName(objectName), gmx); } /** * Creates a new MetaMBean * @param objectName The JMX ObjectName of the MBean * @param gmx The Gmx instance that created this MetaMBean */ private MetaMBean(ObjectName objectName, Gmx gmx) { this.objectName = objectName; this.gmx = gmx; try { mbeanInfo.set(gmx.mbeanServerConnection.getMBeanInfo(objectName)); for(MBeanAttributeInfo minfo: mbeanInfo.get().getAttributes()) { if(minfo.isReadable()) { attributeNames.add(minfo.getName()); } } final StringBuilder scriptBuffer = new StringBuilder(); final Set<String> imports = new HashSet<String>(); for(MBeanOperationInfo minfo: mbeanInfo.get().getOperations()) { final StringBuilder params = new StringBuilder(); final StringBuilder signature = new StringBuilder("["); final StringBuilder values = new StringBuilder("["); addImport(minfo.getReturnType(), imports); scriptBuffer.append("public Object ").append(minfo.getName()).append("("); MBeanParameterInfo[] pinfos = minfo.getSignature(); if(pinfos!=null && pinfos.length>0) { int cnt = 0; for(MBeanParameterInfo pinfo: pinfos) { addImport(pinfo.getType(), imports); params.append(renderTypeName(pinfo.getType())).append(" p").append(cnt).append(","); signature.append("'").append(pinfo.getType()).append("',"); values.append("p").append(cnt).append(","); cnt++; } signature.deleteCharAt(signature.length()-1); values.deleteCharAt(values.length()-1); params.deleteCharAt(params.length()-1); } params.append(") {"); signature.append("] as String[]);"); values.append("] as Object[],"); scriptBuffer.append(params); scriptBuffer.append("\n\treturn server.invoke(objectName, '").append(minfo.getName()).append("', "); scriptBuffer.append(values).append(signature).append("\n}\n"); } scriptBuffer.insert(0, renderImports(imports)); System.out.println(scriptBuffer.toString()); invokerScriptText.set(scriptBuffer.toString()); invokerScript = new GroovyShell().parse(scriptBuffer.toString()); Binding binding = new Binding(); binding.setProperty("server", gmx.getMBeanServerConnection()); binding.setProperty("objectName", objectName); invokerScript.setBinding(binding); } catch (Exception e) { throw new RuntimeException("Failed to acquire MBeanInfo for MBean [" + objectName + "]", e); } } protected String renderImports(final Set<String> imports) { StringBuilder b = new StringBuilder(); for(String s: imports) { b.append(s); } return b.toString(); } protected void addImport(String className, final Set<String> imports) { try { imports.add("import " + Class.forName(className).getPackage().getName() + ".*;\n"); } catch (Exception e) {} } protected String renderTypeName(String name) throws Exception { if(Primitive.isPrimitiveName(name)) { return Primitive.getPrimitive(name).getPclazz().getName(); } Class<?> clazz = Class.forName(name); if(clazz.isArray()) { StringBuilder b = new StringBuilder(); Class<?> arrClass = clazz; do { b.append("[]"); arrClass = arrClass.getComponentType(); } while(arrClass.isArray()); b.insert(0, arrClass.getName()); return b.toString(); } else { return name; } } /** * {@inheritDoc} * @see groovy.lang.GroovyObject#getMetaClass() */ @Override public MetaClass getMetaClass() { if (metaClass == null) { metaClass = GroovySystem.getMetaClassRegistry().getMetaClass(MetaMBean.class); } return metaClass; } /** * {@inheritDoc} * @see groovy.lang.GroovyObject#getProperty(java.lang.String) */ @Override public Object getProperty(String propertyName) { if(attributeNames.contains(propertyName)) { return gmx.mbeanServerConnection.getAttribute(objectName, 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 arg) { return invokerScript.invokeMethod(name, arg); } /** * Generates an array of classes from an array of objects. * @param args The object array * @return an array of classes */ protected Class<?>[] argsToSignagture(Object...args) { Class<?>[] signature = new Class[args.length]; for(int i = 0; i < args.length; i++) { signature[i] = args[i]==null ? null : args[i].getClass(); } return signature; } /** * Finds all operation signatures with an operation name matching the passed name and having the same number of arguments * @param name The op name * @param argCount The argument count * @return a [possibly empty] set of matching operation signatures */ protected Set<OperationSignature> getOpSigs(String name, int argCount) { if(!operations.containsKey(name)) { throw new RuntimeException("Operation name [" + name + "] was not found", new Throwable()); } Set<OperationSignature> set = new HashSet<OperationSignature>(); TreeSet<OperationSignature> opSigs = operations.get(name); for(OperationSignature os: opSigs) { if(os.getArgCount()==argCount) { set.add(os); } else { if(os.getArgCount()>argCount) { break; } } } return set; } /** * {@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) { if(attributeNames.contains(propertyName)) { gmx.mbeanServerConnection.setAttribute(objectName, new Attribute(propertyName, newValue)); } getMetaClass().setProperty(this, propertyName, newValue); } /** * <p>Title: UnknownClass</p> * <p>Description: Synthetic class to represent a unknown class that could not be classloaded</p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.gmx.MetaMBean.UnknownClass</code></p> */ public static class UnknownClass { public boolean equals(Object obj) { return (obj instanceof Class<?>); } } /** * <p>Title: OperationSignature</p> * <p>Description: A container class to provide detailed signature matching of provided arguments to operation signatures.</p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.gmx.MetaMBean.OperationSignature</code></p> */ public static class OperationSignature implements Comparable<OperationSignature> { /** The classes represented in the sgnature */ protected final Class<?>[] signature; /** The classes represented in the sgnature */ protected final String[] strSignature; /** The op name */ protected final String opName; /** * Creates a new OperationSignature * @param info The MBean's MBeanOperationInfo * @return an OperationSignature for the passed MBeanOperationInfo */ public static OperationSignature newInstance(MBeanOperationInfo info) { if(info==null) throw new IllegalArgumentException("The passed info was null", new Throwable()); return new OperationSignature(info.getName(), info.getSignature()); } /** * Returns the number of operation parameters * @return the number of operation parameters */ public int getArgCount() { return strSignature.length; } /** * Creates a new OperationSignature * @param opName The operation name * @param infos An array of the MBean operation info signatures */ private OperationSignature(String opName, MBeanParameterInfo...infos) { this.opName = opName; List<Class<?>> sig = new ArrayList<Class<?>>(infos==null ? 0 : infos.length); List<String> strSig = new ArrayList<String>(infos==null ? 0 : infos.length); if(infos!=null) { for(MBeanParameterInfo pinfo: infos) { String className = pinfo.getType(); strSig.add(className); if(Primitive.isPrimitiveName(className)) { sig.add(Primitive.getPrimitive(className).getPclazz()); } else { Class<?> clazz = null; try { clazz = Class.forName(className); } catch (Exception e) { clazz = UnknownClass.class; } sig.add(clazz); } } } signature = sig.toArray(new Class[sig.size()]); strSignature = strSig.toArray(new String[strSig.size()]); } /** * {@inheritDoc} * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return Arrays.hashCode(strSignature); } /** * {@inheritDoc} * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if(obj instanceof OperationSignature) return Arrays.deepEquals(strSignature, ((OperationSignature)obj).strSignature); return false; } /** * Returns the operation signature * @return the signature */ public Class<?>[] getSignature() { return signature.clone(); } /** * Returns the operation signature as a string array * @return the stringified signature */ public String[] getStrSignature() { return strSignature; } /** * {@inheritDoc} * @see java.lang.Comparable#compareTo(java.lang.Object) */ @Override public int compareTo(OperationSignature os) { Integer thisArgCount = signature.length; Integer thatArgCount = os.signature.length; if(thisArgCount.intValue()==thatArgCount.intValue()) { return (this.hashCode()<os.hashCode() ? -1 : (this.hashCode()==os.hashCode() ? 0 : 1)); } return (thisArgCount<thatArgCount ? -1 : (thisArgCount==thatArgCount ? 0 : 1)); } /** * 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("OperationSignature [") .append(TAB).append("Name = ").append(opName) .append(TAB).append("Arg Count = ").append(signature.length) .append(TAB).append("strSignature = ").append(Arrays.toString(this.strSignature)) .append(TAB).append("hashCode = ").append(this.hashCode()) .append("\n]"); return retValue.toString(); } /** * @return the opName */ public String getOpName() { return opName; } } /** * Registers a notification listener with the MBeanServer on the MBean represented by this MetaMBean * @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(Closure<Void> listener, Closure<Boolean> filter, Object handback, Object...closureArgs ) { if(gmx.isRemote()) { if(!gmx.isRemoted()) { gmx.installRemote(); } } return gmx.addListener(objectName.toString(), listener, filter, handback, closureArgs); } /** * Registers a notification listener with the MBeanServer on the MBean represented by this MetaMBean * @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(Closure<Void> listener, Object...closureArgs) { return addListener(listener, null, null, closureArgs); } // /** // * Registers a notification listener with the MBeanServer on the MBean represented by this MetaMBean // * @param listener A closure that will passed the notification and handback. // * @return The wrapped listener that can be used to unregister the listener // */ // public ObjectNameAwareListener addListener(NotificationListener listener) { // return gmx.addListener() // // } /** * 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() { return new StringBuilder("[") .append(objectName).append("]@") .append(gmx.jvmName==null ? gmx.serverDomain : gmx.jvmName) .toString(); } /** * The JMX ObjectName of the MBean * @return the objectName */ public ObjectName getObjectName() { return objectName; } /** * The MBean's MBeanInfo * @return the mbeanInfo */ public MBeanInfo getMbeanInfo() { return mbeanInfo.get(); } }