package com.bagri.support.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bagri.support.security.LocalSubject;
import com.bagri.support.stats.StatsAggregator;
//import static com.bagri.common.stats.InvocationStatistics.*;
import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.CompositeDataSupport;
import javax.management.openmbean.CompositeType;
import javax.management.openmbean.OpenDataException;
import javax.management.openmbean.OpenType;
import javax.management.openmbean.SimpleType;
import javax.management.openmbean.TabularData;
import javax.management.openmbean.TabularDataSupport;
import javax.management.openmbean.TabularType;
import javax.management.remote.JMXPrincipal;
import javax.security.auth.Subject;
import java.lang.management.ManagementFactory;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collections;
//import java.util.Date;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
/**
* A set of static utility methods for JMX
*
* @author Denis Sukhoroslov
*
*/
public class JMXUtils {
private static final Logger logger = LoggerFactory.getLogger(JMXUtils.class);
public static final String key_type = "type";
public static final String key_name = "name";
public static final String domain = "com.bagri.db";
public static final String type_management = "Management";
/**
* Registers system-specific MBean in domain {@code com.bagri.db} with type {@code Management}
*
* @param name the MBean name
* @param mBean the MBean to register
* @return true if MBean was registered, false otherwise
*/
public static boolean registerMBean(String name, Object mBean) {
Hashtable<String, String> keys = getStandardKeys(type_management, name);
return registerMBean(domain, keys, mBean);
}
/**
* Registers system-specific MBean in domain {@code com.bagri.db}
*
* @param name the MBean name
* @param type the MBean type
* @param mBean the MBean to register
* @return true if MBean was registered, false otherwise
*/
public static boolean registerMBean(String type, String name, Object mBean) {
Hashtable<String, String> keys = getStandardKeys(type, name);
return registerMBean(domain, keys, mBean);
}
/**
* Unregisters MBean from JMX repository
*
* @param type the MBean type
* @param name the MBean name
* @return true if MBean was unregistered, false otherwise
*/
public static boolean unregisterMBean(String type, String name) {
Hashtable<String, String> keys = getStandardKeys(type, name);
return unregisterMBean(domain, keys);
}
/**
* Creates a Hashtable containing type and name MBean properties for futher creation of MBean ObjectName.
*
* @param type the MBena type
* @param name the MBean name
* @return the Hashtable containing the properties above
*/
public static Hashtable<String, String> getStandardKeys(String type, String name) {
Hashtable<String, String> keys = new Hashtable<String, String>(2);
keys.put(key_type, type);
keys.put(key_name, name);
return keys;
}
/**
* Registers MBean under {@code com.bagri.db} domain
*
* @param keys the MBean keys to use for registration
* @param mBean the MBean to register
* @return true if the MBean was registered, false otherwise
*/
public static boolean registerMBean(Hashtable<String, String> keys, Object mBean) {
return registerMBean(domain, keys, mBean);
}
/**
* Produce JMX ObjectName from the MBean type and name provided. Uses {@code com.bagri.db} as domain name
*
* @param type the MBean type
* @param name the MBean name
* @return the MBean ObjectName
* @throws MalformedObjectNameException in case of ObjectName construction error
*/
public static ObjectName getObjectName(String type, String name) throws MalformedObjectNameException {
Hashtable<String, String> keys = getStandardKeys(type, name);
return new ObjectName(domain, keys);
}
/**
* Produce JMX ObjectName from the MBean keys provided. Uses {@code com.bagri.db} as domain name
*
* @param keys the MBean keys
* @return the MBean ObjectName
* @throws MalformedObjectNameException in case of ObjectName construction error
*/
public static ObjectName getObjectName(Hashtable keys) throws MalformedObjectNameException {
return new ObjectName(domain, keys);
}
/**
* Produce JMX ObjectName from the MBean keys provided as String. Uses {@code com.bagri.db} as domain name
*
* @param keys the MBean keys in String format
* @return the MBean name
* @throws MalformedObjectNameException in case of ObjectName construction error
*/
public static ObjectName getObjectName(String keys) throws MalformedObjectNameException {
return new ObjectName(domain + ":" + keys);
}
/**
* Registers MBean in JMX repository
*
* @param domain the domain to register MBean under
* @param keys the MBean keys
* @param mBean the MBean
* @return true if MBean has been registered successfully, false otherwise
*/
public static boolean registerMBean(String domain, Hashtable<String, String> keys, Object mBean) {
ArrayList<MBeanServer> servers = MBeanServerFactory.findMBeanServer(null);
MBeanServer defServer = servers.get(0);
ObjectName name;
try {
name = new ObjectName(domain, keys);
defServer.registerMBean(mBean, name);
return true;
} catch (MalformedObjectNameException | InstanceAlreadyExistsException |
MBeanRegistrationException | NotCompliantMBeanException ex) {
logger.error("register.error: " + ex.getMessage(), ex);
}
return false;
}
/**
* Unregisters MBean from JMX repository. Uses {@code com.bagri.db} domain
*
* @param keys the MBean keys
* @return true if MBean has been unregistered, false otherwise
*/
public static boolean unregisterMBean(Hashtable<String, String> keys) {
return unregisterMBean(domain, keys);
}
/**
* Unregisters MBean from JMX repository
*
* @param domain the domain to unregister MBean from
* @param keys the MBean keys
* @return true if MBean has been unregistered successfully, false otherwise
*/
public static boolean unregisterMBean(String domain, Hashtable<String, String> keys) {
ArrayList<MBeanServer> servers = MBeanServerFactory.findMBeanServer(null);
MBeanServer defServer = servers.get(0);
ObjectName name;
try {
name = new ObjectName(domain, keys);
defServer.unregisterMBean(name);
return true;
} catch (MalformedObjectNameException | MBeanRegistrationException | InstanceNotFoundException ex) {
logger.error("unregister.error: " + ex.getMessage(), ex);
}
return false;
}
/**
* Get the current user name connected to JMX server
*
* @return the current user name if any, null otherwise
*/
public static String getSubjectUser() {
AccessControlContext ctx = AccessController.getContext();
Subject subj = Subject.getSubject(ctx);
String result = null;
if (subj == null) {
subj = LocalSubject.getSubject();
}
logger.trace("getSubjectUser; subject: {}", subj);
if (subj != null) {
Set<JMXPrincipal> sjp = subj.getPrincipals(JMXPrincipal.class);
if (sjp != null && sjp.size() > 0) {
result = sjp.iterator().next().getName();
} else {
Set<Principal> sp = subj.getPrincipals();
if (sp != null && sp.size() > 0) {
result = sp.iterator().next().getName();
}
}
}
logger.trace("getSubjectUser.exit; returning: {}", result);
return result;
}
/**
* Get the current JMX user name or system user name
*
* @return the current JMX user name or system user name. If both are null return "unknown" user name
*/
public static String getCurrentUser() {
String result = getSubjectUser();
if (result == null) {
result = System.getProperty("user.name");
if (result == null) {
result = "unknown";
}
}
logger.trace("getCurrentUser.exit; returning: {}", result);
return result;
}
/**
* Get the current JMX user name or login name
*
* @param login the default user name
* @return the current JMX user name if any, the login name if the current JMX user name is not known
*/
public static String getCurrentUser(String login) {
String result = getSubjectUser();
return result == null ? login : result;
}
/**
* Query MBean names from JMX server.
*
* @param query the JMX query string
* @return the list of matching ObjectNames
*/
public static List<String> queryNames(String query) {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
try {
Set<ObjectName> names = mbs.queryNames(new ObjectName(query), null);
List<String> result = new ArrayList<String>(names.size());
for (ObjectName name: names) {
result.add(name.toString());
}
return result;
} catch (MalformedObjectNameException ex) {
logger.error("queryNames.error: " + ex.getMessage(), ex);
}
return Collections.emptyList();
}
/**
* Converts Java Map to JMX CompositeData structure
*
* @param name the produced CompositeData name
* @param desc the produced CompositeData description
* @param def the Map to convert
* @return the produced Composite data
*/
public static CompositeData mapToComposite(String name, String desc, Map<String, Object> def) {
if (def != null && !def.isEmpty()) {
try {
String[] names = new String[def.size()];
OpenType[] types = new OpenType[def.size()];
int idx = 0;
for (Map.Entry<String, Object> e : def.entrySet()) {
names[idx] = e.getKey();
types[idx] = getOpenType(e.getValue());
idx++;
}
CompositeType type = new CompositeType(name, desc, names, names, types);
return new CompositeDataSupport(type, def);
} catch (Exception ex) {
logger.warn("statsToComposite. error: {}", ex.getMessage());
}
}
return null;
}
/**
* Merges CompositeData part with series of another Composite blocks in TabularData format
*
* @param name the series name
* @param desc the series description
* @param key the series key
* @param source the aggregated data to merge with
* @param data the series data to be merged into the aggregation result
* @return the aggregated result in TabularData format
* @throws OpenDataException in case of data conversion error
*/
public static TabularData compositeToTabular(String name, String desc, String key,
TabularData source, CompositeData data) throws OpenDataException {
if (data == null) {
return source;
}
if (source == null) {
TabularType type = new TabularType(name, desc, data.getCompositeType(), new String[] {key});
source = new TabularDataSupport(type);
}
source.put(data);
//logger.trace("getStatisticSeries; added row: {}", data);
return source;
}
/**
* Aggregates two tabular structures into one.
*
* @param source the source tabular
* @param target the target tabular
* @param aggregator the aggregator which will perform data aggregation
* @return the aggregated tabular structure
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public static TabularData aggregateStats(TabularData source, TabularData target, StatsAggregator aggregator) {
logger.debug("aggregateStats.enter; got source: {}", source);
if (source == null) {
return target;
}
TabularData result = new TabularDataSupport(source.getTabularType());
Set<List> keys = (Set<List>) source.keySet();
if (target == null) {
return source;
} else {
for (List key: keys) {
Object[] index = key.toArray();
CompositeData aggr = aggregateStats(source.get(index), target.get(index), aggregator);
result.put(aggr);
}
}
logger.debug("aggregateStats.exit; returning: {}", result);
return result;
}
/**
* Aggregates two composite structures into one.
*
* @param source the source composite
* @param target the target composite
* @param aggregator the aggregator which will perform data aggregation
* @return the aggregated composite structure
*/
public static CompositeData aggregateStats(CompositeData source, CompositeData target, StatsAggregator aggregator) {
Set<String> keys = source.getCompositeType().keySet();
String[] names = keys.toArray(new String[keys.size()]);
Object[] srcVals = source.getAll(names);
if (target == null) {
try {
target = new CompositeDataSupport(source.getCompositeType(), names, srcVals);
} catch (OpenDataException ex) {
logger.warn("aggregateStats. error: {}", ex.getMessage());
return null;
}
} else {
Object[] trgVals = aggregator.aggregateStats(srcVals, target.getAll(names));
try {
target = new CompositeDataSupport(source.getCompositeType(), names, trgVals);
} catch (OpenDataException ex) {
logger.warn("aggregateStats. error: {}", ex.getMessage());
return null;
}
}
return target;
}
/**
* Converts set of properties to JMX CompositeData structure
*
* @param name the produced CompositeData name
* @param desc the produced CompositeData description
* @param props the Properties to convert
* @return the produced Composite data
*/
public static CompositeData propsToComposite(String name, String desc, Properties props) {
logger.trace("propsToComposite; name: {}; properties: {}", name, props);
if (props != null && !props.isEmpty()) {
try {
String[] names = new String[props.size()];
OpenType[] types = new OpenType[props.size()];
Object[] values = new Object[props.size()];
int idx = 0;
for (Map.Entry<Object, Object> e : props.entrySet()) {
names[idx] = (String) e.getKey();
types[idx] = getOpenType(e.getValue());
values[idx] = e.getValue();
idx++;
}
CompositeType type = new CompositeType(name, desc, names, names, types);
return new CompositeDataSupport(type, names, values);
} catch (/*OpenDataException ex*/ Throwable ex) {
logger.warn("propsToComposite. error: {}", ex.getMessage());
}
}
return null;
}
/**
* Converts JMX CompositeData structure to Java Map
*
* @param props the composite structure
* @return the resulting Java Map
*/
public static Map<String, Object> propsFromComposite(CompositeData props) {
Map<String, Object> result = new HashMap<String, Object>(props.values().size());
CompositeType type = props.getCompositeType();
for (String name : type.keySet()) {
result.put(name, props.get(name));
}
return result;
}
/**
* Converts Map of strings into JMX tabular format. Every name/value pair considered as a separate tab
*
* @param props the source Map
* @return the resulting JMX tabular structure
*/
public static TabularData propsToTabular(Map<String, String> props) {
TabularData result = null;
try {
String typeName = "java.util.Map<java.lang.String, java.lang.String>";
String[] nameValue = new String[]{"name", "value"};
OpenType[] openTypes = new OpenType[]{SimpleType.STRING, SimpleType.STRING};
CompositeType rowType = new CompositeType(typeName, typeName, nameValue, nameValue, openTypes);
TabularType tabularType = new TabularType(typeName, typeName, rowType, new String[]{"name"});
result = new TabularDataSupport(tabularType);
if (props != null && props.size() > 0) {
for (Map.Entry<String, String> prop : props.entrySet()) {
result.put(new CompositeDataSupport(rowType, nameValue, new Object[]{prop.getKey(), prop.getValue()}));
}
}
} catch (OpenDataException ex) {
logger.error("propsToTabular. error: ", ex);
}
return result;
}
/**
* Converts JMX TabularData structure into a Map of strings. Every composite part is should contain name and value parameters
*
* @param props the source TabularData
* @return the resulting properties Map
*/
public static Map<String, String> propsFromTabular(TabularData props) {
Map<String, String> result = new HashMap<String, String>(props.size());
for (Object prop : props.values()) {
CompositeData data = (CompositeData) prop;
result.put((String) data.get("name"), (String) data.get("value"));
}
return result;
}
/**
* Converts Java Map into JMX composite format.
*
* @param props the Map to convert
* @param compositeType the Composite type to use
* @return CompositeData based on type and property Map
*/
public static CompositeData propsToComposite(Map<String, Object> props, CompositeType compositeType) {
CompositeData result = null;
try {
result = new CompositeDataSupport(compositeType, props);
} catch (OpenDataException ex) {
logger.error("propsToComposite. error: ", ex);
}
return result;
}
/**
* Converts JMX composite structure into a List of strings in "name: value" format
*
* @param data the composite data to convert
* @return the resulting name/value list
*/
public static List<String> compositeToStrings(CompositeData data) {
Set<String> keys = data.getCompositeType().keySet();
List<String> result = new ArrayList<String>(keys.size());
for (String key : keys) {
result.add(key + ": " + data.get(key));
}
return result;
}
/**
* Converts Map of properties into a List of strings in "name: value" format
*
* @param props the property Map to convert
* @return the resulting name/value list
*/
public static List<String> propsToStrings(Map<String, Object> props) {
List<String> result = new ArrayList<String>(props.size());
for (Map.Entry<String, Object> e : props.entrySet()) {
result.add(e.getKey() + ": " + e.getValue());
}
return result;
}
private static OpenType getOpenType(Object value) {
if (value == null) {
return SimpleType.VOID;
}
String name = value.getClass().getName();
//if (OpenType.ALLOWED_CLASSNAMES_LIST.contains(name)) {
if ("java.lang.Long".equals(name)) {
return SimpleType.LONG;
}
if ("java.lang.Integer".equals(name)) {
return SimpleType.INTEGER;
}
if ("java.lang.String".equals(name)) {
return SimpleType.STRING;
}
if ("java.lang.Double".equals(name)) {
return SimpleType.DOUBLE;
}
if ("java.lang.Float".equals(name)) {
return SimpleType.FLOAT;
}
if ("java.math.BigDecimal".equals(name)) {
return SimpleType.BIGDECIMAL;
}
if ("java.math.BigInteger".equals(name)) {
return SimpleType.BIGINTEGER;
}
if ("java.lang.Boolean".equals(name)) {
return SimpleType.BOOLEAN;
}
if ("java.lang.Byte".equals(name)) {
return SimpleType.BYTE;
}
if ("java.lang.Character".equals(name)) {
return SimpleType.CHARACTER;
}
if ("java.util.Date".equals(name)) {
return SimpleType.DATE;
}
if ("java.lang.Short".equals(name)) {
return SimpleType.SHORT;
}
//"javax.management.ObjectName",
//CompositeData.class.getName(),
//TabularData.class.getName()
//}
return null; // is it allowed ??
}
}