/*
* Copyright 2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.rioproject.impl.jmx;
import net.jini.core.entry.Entry;
import net.jini.id.Uuid;
import net.jini.lookup.entry.jmx.JMXProperty;
import net.jini.lookup.entry.jmx.JMXProtocolType;
import org.rioproject.config.Constants;
import org.rioproject.servicebean.ServiceBeanContext;
import org.rioproject.opstring.ClassBundle;
import org.rioproject.opstring.ServiceBeanConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.openmbean.*;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* Provides utilities for using JMX.
*
* @author Ming Fang
* @author Dennis Reedy
*/
public class JMXUtil {
private static Logger logger = LoggerFactory.getLogger(JMXUtil.class.getName());
/**
* Get a platform MXBean proxy
*
* @param mbsc The MBeanServerConnection
* @param name The ObjectName to create
* @param mxBeanInterface The platform MXBean interface type to create
* @return A platform MXBean proxy
*/
public static <T> T getPlatformMXBeanProxy(final MBeanServerConnection mbsc,
final String name,
final Class<T> mxBeanInterface) {
T mxBean = null;
try {
ObjectName objName = new ObjectName(name);
mxBean = ManagementFactory.newPlatformMXBeanProxy(mbsc, objName.toString(), mxBeanInterface);
} catch (IOException e) {
logger.warn("Could not create PlatformMXBeanProxy", e);
} catch (MalformedObjectNameException e) {
logger.warn("Could not create PlatformMXBeanProxy", e);
}
return mxBean;
}
/**
* Create a Map of accessor methods for the data object that are supported
* by {@link javax.management.openmbean.OpenType#ALLOWED_CLASSNAMES}
*
* @param data The data to map
*
* @return A Map of accessor methods for the data object that are supported
* by {@link javax.management.openmbean.OpenType#ALLOWED_CLASSNAMES}
* @throws IntrospectionException If an exception occurred during the
* introspection of an MBean
* @throws IllegalAccessException If access permissions result reflecting
* on the MBean
* @throws InvocationTargetException If the MBean cannot be instantiated
*/
public static Map toMap(final Object data) throws
IntrospectionException,
IllegalAccessException,
InvocationTargetException {
Map<String, Object> map = new HashMap<String, Object>();
BeanInfo beanInfo = Introspector.getBeanInfo(data.getClass());
PropertyDescriptor[] propertyDescriptors =
beanInfo.getPropertyDescriptors();
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
String key = propertyDescriptor.getName();
Method readMethod = propertyDescriptor.getReadMethod();
Object value = null;
if (readMethod != null) {
value = readMethod.invoke(data, (Object[]) null);
}
if (value == null) {
continue;
}
if (!isOpenType(key)) {
value = value.toString();
}
map.put(key, value);
}
return map;
}
/**
* Create a {@link javax.management.openmbean.CompositeType}
*
* @param m A Map of accessor methods corresponding to open data types
* @param compositeTypeName The name to use for the composite type, must
* not be null or an empty string
* @param compositeTypeDescription The description to use for the
* composite type, must not be null or an empty string
* @return The created CompositeType
* @throws OpenDataException If the CompositeType cannot be created
*/
public static CompositeType createCompositeType(final Map m,
final String compositeTypeName,
final String compositeTypeDescription) throws OpenDataException {
String [] keys = new String[m.size()];
OpenType [] types = new OpenType[m.size()];
int index = 0;
for (Object o : m.keySet()) {
String key = (String) o;
keys[index] = key;
types[index] = getOpenType(m.get(key).getClass().getName(), null);
index++;
}
return new CompositeType(compositeTypeName, compositeTypeDescription, keys, keys, types);
}
/**
* Get the corresponding OpenType for a fully qualified class name
*
* @param classString A fully qualified class name, suitable for use
* with Class.forName(), must not be null
* @param defaultType The defaultOpenType to use
* @return the corresponding OpenType
*
* @throws IllegalArgumentException if the classString argument is null
* @throws InvalidOpenTypeException if the class described by the
* classString argument is not a valid open type
*/
public static OpenType getOpenType(final String classString, final OpenType defaultType) {
if(classString==null)
throw new IllegalArgumentException("classString is null");
if(classString.equals("void")) {
return SimpleType.VOID;
}
if(!isOpenType(classString)) {
throw new InvalidOpenTypeException(classString);
}
if(classString.equals(String.class.getName())) {
return SimpleType.STRING;
} else if(classString.equals(Boolean.class.getName())) {
return SimpleType.BOOLEAN;
} else if(classString.equals(Long.class.getName())) {
return SimpleType.LONG;
} else if(classString.equals(Integer.class.getName())) {
return SimpleType.INTEGER;
} else if(classString.equals(Float.class.getName())) {
return SimpleType.FLOAT;
} else if(classString.equals(Double.class.getName())) {
return SimpleType.DOUBLE;
} else if(defaultType != null) {
return defaultType;
}
throw new InvalidOpenTypeException("Unsupported type: "+classString);
}
/**
* Determine if the class name is an OpenType
*
* @param className A fully qualified class name, suitable for use
* with Class.forName(), must not be null
* @return If the class name is supported as an OpenType return
* <code>true</code>
*
* @see javax.management.openmbean.OpenType#ALLOWED_CLASSNAMES_LIST
*/
public static boolean isOpenType(final String className) {
return OpenType.ALLOWED_CLASSNAMES_LIST.contains(className);
}
/**
* Get the JMX name to use as a base name for the ObjectName. Key property
* list values should be appended to the value to create ObjectName
* instances.
*
* This method will check to see if the
* {@link org.rioproject.opstring.ServiceBeanConfig#JMX_NAME} property exists,
* if it does not, the default domain will be used as a basis to create
* the name, and the property will be added to the context with the
* following format :
* <tt>defaultDomain:type=<export-class-name>,name=servicename</tt>
*
* @param context The ServiceBeanContext, must not be null
* @param defaultDomain The default domain to use if the
* ServiceBeanConfig.JMX_NAME property is not found
*
* @return A String to use as a base name for the ObjectName. The returned
* value will have the name with the format of:
* <tt>jmxName,uuid=uuid-string</tt>
*
*/
public static String getJMXName(final ServiceBeanContext context,
final String defaultDomain) {
if(context==null)
throw new IllegalArgumentException("context is null");
Uuid uuid = context.getServiceBeanManager().getServiceID();
Map<String, Object> configParms = context.getServiceBeanConfig().getConfigurationParameters();
String jmxName = (String)configParms.get(ServiceBeanConfig.JMX_NAME);
ClassBundle[] exports = context.getServiceElement().getExportBundles();
String type = "Service";
String domain = null;
if(exports.length>0) {
String exportClass = exports[0].getClassName();
int ndx = exportClass.lastIndexOf(".");
if(ndx>0) {
type = exportClass.substring(ndx+1);
domain = exportClass.substring(0, ndx);
}
}
if(jmxName==null) {
jmxName = (domain==null?defaultDomain:domain)+":"+"type="+type;
configParms.put(ServiceBeanConfig.JMX_NAME, jmxName);
context.getServiceBeanConfig().setConfigurationParameters(configParms);
}
return (jmxName+","+"uuid="+uuid);
}
/**
* Get an ObjectName with the following format :
*
* <tt>defaultDomain:type=<export-class-name>,name=servicename,
* name=<name>, id=<id></tt>
*
* @param context The ServiceBeanContext, must not be null
* @param defaultDomain The default domain to use if the
* ServiceBeanConfig.JMX_NAME property is not found
* @param name The name to use
* @return The created ObjectName
* @throws MalformedObjectNameException If the constructed name is malformed
*/
public static ObjectName getObjectName(final ServiceBeanContext context,
final String defaultDomain,
final String name) throws MalformedObjectNameException {
String jmxName = getJMXName(context, defaultDomain);
String oName = jmxName+","+"name="+name;
return ObjectName.getInstance(oName);
}
/**
* Get an ObjectName with the following format :
* <p/>
* <tt>defaultDomain:type=<export-class-name>,name=servicename, name=<name>,
* id=<id></tt>
*
* @param context The ServiceBeanContext, must not be null
* @param defaultDomain The default domain to use if the
* ServiceBeanConfig.JMX_NAME property is not found
* @param name The jmxName to use
* @param id The instanceID of the bean
*
* @return An ObjectName based on the ServiceBeanContext, defaultDomain,
* name and id
*
* @throws MalformedObjectNameException If the constructed name is malformed
*/
public static ObjectName getObjectName(final ServiceBeanContext context,
final String defaultDomain,
final String name,
final String id) throws MalformedObjectNameException {
String jmxName = getJMXName(context, defaultDomain);
String oName = jmxName+","+"name="+name+","+"id="+id;
return (ObjectName.getInstance(oName));
}
/**
* Get the attributes to add to a service's attribute collection
*
* @return An array of {@link net.jini.core.entry.Entry}s. If the
* <tt>org.rioproject.jmxServiceURL</tt> system property exists (is not null)
* create an array of 2 attributes, one being
* {@link net.jini.lookup.entry.jmx.JMXProtocolType} with the protocol
* type set to {@link net.jini.lookup.entry.jmx.JMXProtocolType#RMI}, the other
* {@link net.jini.lookup.entry.jmx.JMXProperty}, set to the value of the property
* <tt>org.rioproject.jmxServiceURL</tt>. If the
* <tt>org.rioproject.jmxServiceURL</tt> property is not found, return a
* zero-length array. A new array is created each time.
*/
public static Entry[] getJMXConnectionEntries() {
Entry[] entries = new Entry[0];
/* Check for JMXConnection */
try {
JMXConnectionUtil.createJMXConnection();
String jmxServiceURL = System.getProperty(Constants.JMX_SERVICE_URL);
if(jmxServiceURL!=null) {
entries = new Entry[2];
entries[0] = new JMXProtocolType(JMXProtocolType.RMI);
entries[1] = new JMXProperty(Constants.JMX_SERVICE_URL, jmxServiceURL);
}
} catch (Exception e) {
logger.warn("Could not create JMX Connection, JMX monitoring not available", e);
}
return(entries);
}
/**
* Get the String value found in the JMXProperty entry, or null if the attribute
* set does not include a JMXProperty
*
* @param attributes An array of Entry attributes
*
* @return The JMX Connection String obtained from a
*/
public static String getJMXConnection(final Entry[] attributes) {
String jmxConn = null;
for (Entry attribute : attributes) {
if (attribute instanceof JMXProperty) {
JMXProperty jmxProp = ((JMXProperty) attribute);
if (jmxProp.name != null &&
jmxProp.name.equals(Constants.JMX_SERVICE_URL)) {
jmxConn = jmxProp.value;
break;
}
}
}
return(jmxConn);
}
}