package com.j256.simplejmx.client; import java.io.Closeable; import java.io.IOException; import java.net.InetAddress; import java.net.MalformedURLException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.management.Attribute; import javax.management.AttributeList; import javax.management.JMException; import javax.management.MBeanAttributeInfo; import javax.management.MBeanInfo; import javax.management.MBeanOperationInfo; import javax.management.MBeanParameterInfo; import javax.management.MBeanServerConnection; import javax.management.ObjectName; import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServiceURL; import com.j256.simplejmx.common.IoUtils; import com.j256.simplejmx.common.ObjectNameUtil; /** * JMX client connection implementation which connects to a JMX server and gets JMX information, gets/sets attributes, * and invokes operations. * * @author graywatson */ public class JmxClient implements Closeable { private JMXConnector jmxConnector; private JMXServiceURL serviceUrl; private MBeanServerConnection mbeanConn; private final static Map<String, String> primitiveObjectMap = new HashMap<String, String>(); static { primitiveObjectMap.put(boolean.class.getName(), Boolean.class.getName()); primitiveObjectMap.put(byte.class.getName(), Byte.class.getName()); primitiveObjectMap.put(char.class.getName(), Character.class.getName()); primitiveObjectMap.put(short.class.getName(), Short.class.getName()); primitiveObjectMap.put(int.class.getName(), Integer.class.getName()); primitiveObjectMap.put(long.class.getName(), Long.class.getName()); primitiveObjectMap.put(float.class.getName(), Float.class.getName()); primitiveObjectMap.put(double.class.getName(), Double.class.getName()); // NOTE: don't need void/Void } /** * <p> * Connect the client to a JMX server using the full JMX URL format. The URL should look something like: * </p> * * <pre> * service:jmx:rmi:///jndi/rmi://hostName:portNumber/jmxrmi * </pre> */ public JmxClient(String jmxUrl) throws JMException { this(jmxUrl, null); } /** * <p> * Connect the client to a JMX server using the full JMX URL format with username/password credentials. The URL * should look something like: * </p> * * <pre> * service:jmx:rmi:///jndi/rmi://hostName:portNumber/jmxrmi * </pre> */ public JmxClient(String jmxUrl, String userName, String password) throws JMException { this(jmxUrl, addCredentialsToMap(userName, password, null)); } /** * <p> * Connect the client to a JMX server using the full JMX URL format with username/password credentials. The URL * should look something like: * </p> * * <pre> * service:jmx:rmi:///jndi/rmi://hostName:portNumber/jmxrmi * </pre> */ public JmxClient(String jmxUrl, String userName, String password, Map<String, Object> environmentMap) throws JMException { this(jmxUrl, addCredentialsToMap(userName, password, environmentMap)); } /** * Connect the client to the local host at a certain port number. */ public JmxClient(int localPort) throws JMException { this(generalJmxUrlForHostNamePort("", localPort), null); } /** * Connect the client to a host and port combination. */ public JmxClient(String hostName, int port) throws JMException { this(generalJmxUrlForHostNamePort(hostName, port), null); } /** * Connect the client to a host and port combination and a username and password. */ public JmxClient(String hostName, int port, String userName, String password) throws JMException { this(generalJmxUrlForHostNamePort(hostName, port), addCredentialsToMap(userName, password, null)); } /** * Connect the client to a host and port combination. */ public JmxClient(String hostName, int port, Map<String, Object> environment) throws JMException { this(generalJmxUrlForHostNamePort(hostName, port), environment); } /** * Connect the client to an address and port combination. */ public JmxClient(InetAddress address, int port) throws JMException { this(generalJmxUrlForHostNamePort(address.getHostAddress(), port), null); } /** * <p> * Connect the client to a JMX server using the full JMX URL format an environment-map passed into * {@link JMXConnectorFactory#connect(JMXServiceURL, Map)}. The URL should look something like: * </p> * * <pre> * service:jmx:rmi:///jndi/rmi://hostName:portNumber/jmxrmi * </pre> */ public JmxClient(String jmxUrl, Map<String, Object> environmentMap) throws JMException { if (jmxUrl == null) { throw new IllegalArgumentException("Jmx URL cannot be null"); } try { this.serviceUrl = new JMXServiceURL(jmxUrl); } catch (MalformedURLException e) { throw createJmException("JmxServiceUrl was malformed: " + jmxUrl, e); } try { jmxConnector = JMXConnectorFactory.connect(serviceUrl, environmentMap); mbeanConn = jmxConnector.getMBeanServerConnection(); } catch (IOException e) { if (jmxConnector != null) { IoUtils.closeQuietly(jmxConnector); jmxConnector = null; } throw createJmException("Problems connecting to the server" + e, e); } } /** * Returns a JMX/RMI URL for a host-name and port. */ public static String generalJmxUrlForHostNamePort(String hostName, int port) { return "service:jmx:rmi:///jndi/rmi://" + hostName + ":" + port + "/jmxrmi"; } /** * Close the client connection to the mbean server.If you want a method that throws then use {@link #closeThrow()}. */ @Override public void close() { try { closeThrow(); } catch (JMException e) { // ignored } } /** * Close the client connection to the mbean server. If you want a method that does not throw then use * {@link #close()}. */ public void closeThrow() throws JMException { try { if (jmxConnector != null) { jmxConnector.close(); jmxConnector = null; } // NOTE: doesn't seem to be close method on MBeanServerConnection mbeanConn = null; } catch (IOException e) { throw createJmException("Could not close the jmx connector", e); } } /** * Return an array of the bean's domain names. */ public String[] getBeanDomains() throws JMException { checkClientConnected(); try { return mbeanConn.getDomains(); } catch (IOException e) { throw createJmException("Problems getting jmx domains: " + e, e); } } /** * Return a set of the various bean ObjectName objects associated with the Jmx server. */ public Set<ObjectName> getBeanNames() throws JMException { checkClientConnected(); try { return mbeanConn.queryNames(null, null); } catch (IOException e) { throw createJmException("Problems querying for jmx bean names: " + e, e); } } /** * Return a set of the various bean ObjectName objects associated with the Jmx server. */ public Set<ObjectName> getBeanNames(String domain) throws JMException { checkClientConnected(); try { return mbeanConn.queryNames(ObjectName.getInstance(domain + ":*"), null); } catch (IOException e) { throw createJmException("Problems querying for jmx bean names: " + e, e); } } /** * Return an array of the attributes associated with the bean name. */ public MBeanAttributeInfo[] getAttributesInfo(String domainName, String beanName) throws JMException { return getAttributesInfo(ObjectNameUtil.makeObjectName(domainName, beanName)); } /** * Return an array of the attributes associated with the bean name. */ public MBeanAttributeInfo[] getAttributesInfo(ObjectName name) throws JMException { checkClientConnected(); try { return mbeanConn.getMBeanInfo(name).getAttributes(); } catch (Exception e) { throw createJmException("Problems getting bean information from " + name, e); } } /** * Return information for a particular attribute name. */ public MBeanAttributeInfo getAttributeInfo(ObjectName name, String attrName) throws JMException { checkClientConnected(); return getAttrInfo(name, attrName); } /** * Return an array of the operations associated with the bean name. */ public MBeanOperationInfo[] getOperationsInfo(String domainName, String beanName) throws JMException { return getOperationsInfo(ObjectNameUtil.makeObjectName(domainName, beanName)); } /** * Return an array of the operations associated with the bean name. */ public MBeanOperationInfo[] getOperationsInfo(ObjectName name) throws JMException { checkClientConnected(); try { return mbeanConn.getMBeanInfo(name).getOperations(); } catch (Exception e) { throw createJmException("Problems getting bean information from " + name, e); } } /** * Return an array of the operations associated with the bean name. */ public MBeanOperationInfo getOperationInfo(ObjectName name, String oper) throws JMException { checkClientConnected(); MBeanInfo mbeanInfo; try { mbeanInfo = mbeanConn.getMBeanInfo(name); } catch (Exception e) { throw createJmException("Problems getting bean information from " + name, e); } for (MBeanOperationInfo info : mbeanInfo.getOperations()) { if (oper.equals(info.getName())) { return info; } } return null; } /** * Return the value of a JMX attribute. */ public Object getAttribute(String domain, String beanName, String attributeName) throws Exception { return getAttribute(ObjectNameUtil.makeObjectName(domain, beanName), attributeName); } /** * Return the value of a JMX attribute. */ public Object getAttribute(ObjectName name, String attributeName) throws Exception { checkClientConnected(); return mbeanConn.getAttribute(name, attributeName); } /** * Return the value of a JMX attribute as a String. */ public String getAttributeString(String domain, String beanName, String attributeName) throws Exception { return getAttributeString(ObjectNameUtil.makeObjectName(domain, beanName), attributeName); } /** * Return the value of a JMX attribute as a String or null if attribute has a null value. */ public String getAttributeString(ObjectName name, String attributeName) throws Exception { Object bean = getAttribute(name, attributeName); if (bean == null) { return null; } else { return ClientUtils.valueToString(bean); } } /** * Get multiple attributes at once from the server. */ public List<Attribute> getAttributes(ObjectName name, String[] attributes) throws Exception { checkClientConnected(); return mbeanConn.getAttributes(name, attributes).asList(); } /** * Get multiple attributes at once from the server. */ public List<Attribute> getAttributes(String domain, String beanName, String[] attributes) throws Exception { checkClientConnected(); return getAttributes(ObjectNameUtil.makeObjectName(domain, beanName), attributes); } /** * Set the JMX attribute to a particular value string. */ public void setAttribute(String domainName, String beanName, String attrName, String value) throws Exception { setAttribute(ObjectNameUtil.makeObjectName(domainName, beanName), attrName, value); } /** * Set the JMX attribute to a particular value string. */ public void setAttribute(ObjectName name, String attrName, String value) throws Exception { MBeanAttributeInfo info = getAttrInfo(name, attrName); if (info == null) { throw new IllegalArgumentException("Cannot find attribute named '" + attrName + "'"); } else { setAttribute(name, attrName, ClientUtils.stringToParam(value, info.getType())); } } /** * Set the JMX attribute to a particular value string. */ public void setAttribute(String domainName, String beanName, String attrName, Object value) throws Exception { setAttribute(ObjectNameUtil.makeObjectName(domainName, beanName), attrName, value); } /** * Set the JMX attribute to a particular value. */ public void setAttribute(ObjectName name, String attrName, Object value) throws Exception { checkClientConnected(); Attribute attribute = new Attribute(attrName, value); mbeanConn.setAttribute(name, attribute); } /** * Set a multiple attributes at once on the server. */ public void setAttributes(ObjectName name, List<Attribute> attributes) throws Exception { checkClientConnected(); mbeanConn.setAttributes(name, new AttributeList(attributes)); } /** * Set a multiple attributes at once on the server. */ public void setAttributes(String domainName, String beanName, List<Attribute> attributes) throws Exception { setAttributes(ObjectNameUtil.makeObjectName(domainName, beanName), attributes); } /** * Invoke a JMX method with a domain/object-name as an array of parameter strings. * * @return The value returned by the method or null if none. */ public Object invokeOperation(String domain, String beanName, String operName, String... paramStrings) throws Exception { if (paramStrings.length == 0) { return invokeOperation(ObjectNameUtil.makeObjectName(domain, beanName), operName, null, null); } else { return invokeOperation(ObjectNameUtil.makeObjectName(domain, beanName), operName, paramStrings); } } /** * Invoke a JMX method as an array of parameter strings. * * @return The value returned by the method or null if none. */ public Object invokeOperation(ObjectName name, String operName, String... paramStrings) throws Exception { String[] paramTypes = lookupParamTypes(name, operName, paramStrings); Object[] paramObjs; if (paramStrings.length == 0) { paramObjs = null; } else { paramObjs = new Object[paramStrings.length]; for (int i = 0; i < paramStrings.length; i++) { paramObjs[i] = ClientUtils.stringToParam(paramStrings[i], paramTypes[i]); } } return invokeOperation(name, operName, paramTypes, paramObjs); } /** * Invoke a JMX method as an array of parameter strings. * * @return The value returned by the method as a string or null if none. */ public String invokeOperationToString(ObjectName name, String operName, String... paramStrings) throws Exception { return ClientUtils.valueToString(invokeOperation(name, operName, paramStrings)); } /** * Invoke a JMX method as an array of objects. * * @return The value returned by the method or null if none. */ public Object invokeOperation(String domain, String beanName, String operName, Object... params) throws Exception { return invokeOperation(ObjectNameUtil.makeObjectName(domain, beanName), operName, params); } /** * Invoke a JMX method as an array of objects. * * @return The value returned by the method or null if none. */ public Object invokeOperation(ObjectName objectName, String operName, Object... params) throws Exception { String[] paramTypes = lookupParamTypes(objectName, operName, params); return invokeOperation(objectName, operName, paramTypes, params); } private static Map<String, Object> addCredentialsToMap(String userName, String password, Map<String, Object> environmentMap) { if (environmentMap == null) { environmentMap = new HashMap<String, Object>(); } if (userName != null || password != null) { String[] credentials = new String[] { userName, password }; environmentMap.put(JMXConnector.CREDENTIALS, credentials); } return environmentMap; } private Object invokeOperation(ObjectName objectName, String operName, String[] paramTypes, Object[] params) throws Exception { if (params != null && params.length == 0) { params = null; } return mbeanConn.invoke(objectName, operName, params, paramTypes); } private String[] lookupParamTypes(ObjectName objectName, String operName, Object[] params) throws JMException { checkClientConnected(); MBeanOperationInfo[] operations; try { operations = mbeanConn.getMBeanInfo(objectName).getOperations(); } catch (Exception e) { throw createJmException("Cannot get attribute info from " + objectName, e); } String[] paramTypes = new String[params.length]; for (int i = 0; i < params.length; i++) { paramTypes[i] = params[i].getClass().getName(); } int nameC = 0; String[] first = null; for (MBeanOperationInfo info : operations) { if (!info.getName().equals(operName)) { continue; } MBeanParameterInfo[] mbeanParams = info.getSignature(); if (params.length != mbeanParams.length) { continue; } String[] signatureTypes = new String[mbeanParams.length]; for (int i = 0; i < params.length; i++) { signatureTypes[i] = mbeanParams[i].getType(); } if (paramTypes.length == signatureTypes.length) { boolean found = true; for (int i = 0; i < paramTypes.length; i++) { if (!isClassNameEquivalent(paramTypes[i], signatureTypes[i])) { found = false; break; } } if (found) { return signatureTypes; } } first = signatureTypes; nameC++; } if (first == null) { throw new IllegalArgumentException("Cannot find operation named '" + operName + "'"); } else if (nameC > 1) { throw new IllegalArgumentException( "Cannot find operation named '" + operName + "' with matching argument types"); } else { // return the first one we found that matches the name return first; } } private boolean isClassNameEquivalent(String className1, String className2) { return getWrapperClass(className1).equals(getWrapperClass(className2)); } private String getWrapperClass(String className) { String wrapperClassName = primitiveObjectMap.get(className); if (wrapperClassName == null) { return className; } else { return wrapperClassName; } } private void checkClientConnected() { if (mbeanConn == null) { throw new IllegalArgumentException("JmxClient is not connected"); } } private MBeanAttributeInfo getAttrInfo(ObjectName objectName, String attrName) throws JMException { MBeanAttributeInfo[] attributes; try { attributes = mbeanConn.getMBeanInfo(objectName).getAttributes(); } catch (Exception e) { throw createJmException("Cannot get attribute info from " + objectName, e); } for (MBeanAttributeInfo info : attributes) { if (info.getName().equals(attrName)) { return info; } } return null; } private JMException createJmException(String message, Exception e) { JMException jmException = new JMException(message); jmException.initCause(e); return jmException; } }