/** * NOTE: This copyright does *not* cover user programs that use HQ * program services by normal system calls through the application * program interfaces provided as part of the Hyperic Plug-in Development * Kit or the Hyperic Client Development Kit - this is merely considered * normal use of the program, and does *not* fall under the heading of * "derived work". * * Copyright (C) [2009-2010], VMware, Inc. * This file is part of HQ. * * HQ is free software; you can redistribute it and/or modify * it under the terms version 2 of the GNU General Public License as * published by the Free Software Foundation. This program 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 General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA. * */ package org.hyperic.hq.product.jmx; import java.io.IOException; import java.util.Arrays; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.Map; import java.util.Properties; import java.util.Set; import javax.management.Descriptor; import javax.management.InstanceNotFoundException; import javax.management.IntrospectionException; import javax.management.MBeanAttributeInfo; import javax.management.MBeanInfo; import javax.management.MBeanOperationInfo; import javax.management.MBeanServerConnection; import javax.management.ObjectName; import javax.management.ReflectionException; import javax.management.modelmbean.ModelMBeanAttributeInfo; import javax.management.modelmbean.ModelMBeanInfo; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hyperic.hq.measurement.MeasurementConstants; import org.hyperic.hq.product.MeasurementInfo; import org.hyperic.hq.product.MeasurementInfos; import org.hyperic.hq.product.Metric; import org.hyperic.hq.product.ProductPlugin; import org.hyperic.hq.product.ServerTypeInfo; import org.hyperic.hq.product.ServiceType; import org.hyperic.hq.product.ServiceTypeInfo; import org.hyperic.util.config.ConfigResponse; import org.hyperic.util.config.ConfigSchema; import org.hyperic.util.config.StringConfigOption; import org.hyperic.util.filter.TokenReplacer; /** * Constructs a {@link ServiceType} object from a JMX {@link ModelMBeanInfo} * * @author jhickey * */ public class ServiceTypeFactory { private static final Set VALID_CATEGORIES = new HashSet(Arrays .asList(MeasurementConstants.VALID_CATEGORIES)); private static final Set VALID_UNITS = new HashSet(Arrays .asList(MeasurementConstants.VALID_UNITS)); private Log log = LogFactory.getLog(ServiceTypeFactory.class); private void addControlActions(final ServiceType serviceType, ModelMBeanInfo serviceInfo) { Set actions = new HashSet(); final MBeanOperationInfo[] operations = serviceInfo.getOperations(); for (int i = 0; i < operations.length; i++) { actions.add(operations[i].getName()); } serviceType.setControlActions(actions); } private void addCustomProperties(final ServiceType serviceType, ModelMBeanInfo serviceInfo) { final ConfigSchema propertiesSchema = new ConfigSchema(); final MBeanAttributeInfo[] attributes = serviceInfo.getAttributes(); for (int i = 0; i < attributes.length; i++) { if (!isMetric(attributes[i])) { String description = attributes[i].getDescription(); if(description == null) { description = ""; } propertiesSchema.addOption(new StringConfigOption(attributes[i] .getName(), description)); // custom properties are currently modified by adding a "set" // control action if (attributes[i].isWritable()) { serviceType.addControlAction( "set" + attributes[i].getName()); } } } serviceType.setCustomProperties(propertiesSchema); } private void addFilter(String key, Properties properties, TokenReplacer replacer) { String val = properties.getProperty(key); if (val != null) { replacer.addFilter(key, val); } } private void addMeasurements(final ServiceType serviceType, final ProductPlugin productPlugin, ModelMBeanInfo serviceInfo) { MeasurementInfos measurements = new MeasurementInfos(); final MBeanAttributeInfo[] attributes = serviceInfo.getAttributes(); for (int i = 0; i < attributes.length; i++) { if (isMetric(attributes[i])) { measurements.addMeasurementInfo(createMeasurementInfo( serviceType, productPlugin, (ModelMBeanAttributeInfo) attributes[i])); } } measurements.addMeasurementInfo(createAvailabilityMeasurement( serviceType, productPlugin)); serviceType.setMeasurements(measurements); } private void addMeasurementTemplate(Properties measurementProperties, ProductPlugin productPlugin, ServiceType serviceType) { TokenReplacer replacer = new TokenReplacer(); final String objectName = serviceType.getProperties().getValue( serviceType.getInfo().getName() + ".OBJECT_NAME"); addFilter(MeasurementInfo.ATTR_ALIAS, measurementProperties, replacer); addFilter(MeasurementInfo.ATTR_NAME, measurementProperties, replacer); replacer.addFilter("OBJECT_NAME", objectName); final String template = filter(productPlugin.getPluginProperty(MeasurementInfo.ATTR_TEMPLATE), productPlugin, replacer); measurementProperties.put(MeasurementInfo.ATTR_TEMPLATE, template); } private void addPlugins(final ServiceType serviceType, final ProductPlugin productPlugin) { final ConfigResponse pluginClasses = new ConfigResponse(); pluginClasses.setValue("measurement", productPlugin.getPluginProperty("measurement-class")); pluginClasses.setValue("control", productPlugin.getPluginProperty("control-class")); serviceType.setPluginClasses(pluginClasses); } private void addProperties(final ServiceType serviceType, ObjectName objectName) { final ConfigResponse properties = new ConfigResponse(); properties.setValue(serviceType.getInfo().getName() + ".NAME", serviceType.getServiceName()); properties.setValue(serviceType.getInfo().getName() + ".OBJECT_NAME", getObjectNameProperty(objectName)); serviceType.setProperties(properties); } /** * Creates a Set of ServiceTypes from a Set of Services, ignoring multiple * services of the same ServiceType (determined by fully qualified service * type name) * * @param productPlugin * The plugin of the product containing this service type * @param serverType * The type of service containing this service type * @param mServer * @param serviceInfo * The unique info of the service type * @param objectNames * The {@link ObjectName}s of the associated services whose * metadata is to be inspected * @return A Set of created {@link ServiceType}s created * @throws InstanceNotFoundException * @throws IntrospectionException * @throws ReflectionException * @throws IOException */ public Set create(ProductPlugin productPlugin, ServerTypeInfo serverType, MBeanServerConnection mServer, Set objectNames) throws InstanceNotFoundException, IntrospectionException, ReflectionException, IOException { final Set serviceTypes = new HashSet(); for (Iterator iterator = objectNames.iterator(); iterator.hasNext();) { final ObjectName objectName = (ObjectName) iterator.next(); final MBeanInfo serviceInfo = mServer.getMBeanInfo(objectName); if (serviceInfo instanceof ModelMBeanInfo) { ServiceType identityType = getServiceType(productPlugin.getName(),serverType, (ModelMBeanInfo) serviceInfo, objectName); if (identityType != null && !serviceTypes.contains(identityType)) { final ServiceType serviceType = create(productPlugin, serverType, (ModelMBeanInfo) serviceInfo, objectName); if (serviceType != null) { serviceTypes.add(serviceType); } } } } return serviceTypes; } /** * * @param productPlugin * The plugin of the product containing this service type * @param serverType * The type of service containing this service type * @param serviceInfo * The unique info of the service type * @param objectName * The {@link ObjectName} of the associated service whose * metadata is to be inspected * @return The created {@link ServiceType} or null if it could not be * created */ public ServiceType create(ProductPlugin productPlugin, ServerTypeInfo serverType, ModelMBeanInfo serviceInfo, ObjectName objectName) { ServiceType serviceType = getServiceType(productPlugin.getName(),serverType, serviceInfo, objectName); if(serviceType == null) { return null; } addControlActions(serviceType, serviceInfo); addCustomProperties(serviceType, serviceInfo); addProperties(serviceType, objectName); addPlugins(serviceType, productPlugin); addMeasurements(serviceType, productPlugin, serviceInfo); return serviceType; } private MeasurementInfo createAvailabilityMeasurement( final ServiceType serviceType, final ProductPlugin productPlugin) { Properties measurementProperties = new Properties(); measurementProperties.put(MeasurementInfo.ATTR_UNITS, MeasurementConstants.UNITS_PERCENTAGE); measurementProperties.put(MeasurementInfo.ATTR_NAME, Metric.ATTR_AVAIL); measurementProperties .put(MeasurementInfo.ATTR_ALIAS, Metric.ATTR_AVAIL); measurementProperties.put(MeasurementInfo.ATTR_COLLECTION_TYPE, "dynamic"); measurementProperties.put(MeasurementInfo.ATTR_CATEGORY, MeasurementConstants.CAT_AVAILABILITY); measurementProperties.put(MeasurementInfo.ATTR_INDICATOR, "true"); measurementProperties.put(MeasurementInfo.ATTR_DEFAULTON, "true"); measurementProperties.put(MeasurementInfo.ATTR_INTERVAL, "600000"); addMeasurementTemplate(measurementProperties, productPlugin, serviceType); return createMeasurementInfo(measurementProperties); } private MeasurementInfo createMeasurementInfo( Properties measurementProperties) { MeasurementInfo metric = new MeasurementInfo(); try { metric.setAttributes(measurementProperties); } catch (Exception e) { log.warn("Error setting metric attributes. Cause: " + e.getMessage()); } metric.setCategory(metric.getCategory().toUpperCase()); return metric; } private MeasurementInfo createMeasurementInfo( final ServiceType serviceType, final ProductPlugin productPlugin, ModelMBeanAttributeInfo attribute) { Properties measurementProperties = new Properties(); Descriptor descriptor = attribute.getDescriptor(); String units = (String) descriptor .getFieldValue(MeasurementInfo.ATTR_UNITS); if ("s".equals(units)) { units = MeasurementConstants.UNITS_SECONDS; } if (!VALID_UNITS.contains(units)) { measurementProperties.put(MeasurementInfo.ATTR_UNITS, "none"); } else { measurementProperties.put(MeasurementInfo.ATTR_UNITS, units); } final String displayName = (String) descriptor .getFieldValue("displayName"); // Not likely to be null, as JMX impl currently populates it with // attribute name if not set if (displayName == null) { measurementProperties.put(MeasurementInfo.ATTR_NAME, attribute .getName()); } else { measurementProperties.put(MeasurementInfo.ATTR_NAME, displayName); } measurementProperties.put(MeasurementInfo.ATTR_ALIAS, attribute .getName()); String metricType = (String) descriptor.getFieldValue("metricType"); if(metricType == null || !("COUNTER".equals(metricType.toUpperCase()))) { //GAUGE measurementProperties.put(MeasurementInfo.ATTR_COLLECTION_TYPE,"dynamic"); measurementProperties.put(MeasurementInfo.ATTR_INTERVAL, "300000"); } else { //COUNTER measurementProperties.put(MeasurementInfo.ATTR_COLLECTION_TYPE, "trendsup"); String rate = (String) descriptor.getFieldValue("rate"); if(rate != null) { measurementProperties.put(MeasurementInfo.ATTR_RATE, rate); }else { measurementProperties.put(MeasurementInfo.ATTR_RATE, "none"); } measurementProperties.put(MeasurementInfo.ATTR_INTERVAL, "600000"); } String collectionInterval = (String) descriptor.getFieldValue("collectionInterval"); if(collectionInterval != null) { try { Long.valueOf(collectionInterval); measurementProperties.put(MeasurementInfo.ATTR_INTERVAL, collectionInterval); }catch(NumberFormatException e) { log.warn("Specified collection interval " + collectionInterval + " is not numeric. Default value will be used instead."); } } String category = (String) descriptor.getFieldValue("metricCategory"); if (category == null || !VALID_CATEGORIES.contains(category.toUpperCase())) { measurementProperties.put(MeasurementInfo.ATTR_CATEGORY, MeasurementConstants.CAT_UTILIZATION); } else { measurementProperties.put(MeasurementInfo.ATTR_CATEGORY, category .toUpperCase()); } String indicator = (String) descriptor.getFieldValue("indicator"); if(indicator == null || "true".equals(indicator.toLowerCase())) { //indicator is not in Spring 3.0 @ManagedMetric. Turn measurement on and make indicator by default measurementProperties.put(MeasurementInfo.ATTR_INDICATOR, "true"); measurementProperties.put(MeasurementInfo.ATTR_DEFAULTON, "true"); } else { measurementProperties.put(MeasurementInfo.ATTR_INDICATOR, "false"); measurementProperties.put(MeasurementInfo.ATTR_DEFAULTON, "false"); } String defaultOn = (String) descriptor.getFieldValue("defaultOn"); if(defaultOn != null) { if("true".equals(defaultOn.toLowerCase()) || "false".equals(defaultOn.toLowerCase())) { measurementProperties.put(MeasurementInfo.ATTR_DEFAULTON, defaultOn.toLowerCase()); } else{ log.warn("Invalid value of " + defaultOn + " specified for defaultOn. Default value will be used instead."); } } addMeasurementTemplate(measurementProperties, productPlugin, serviceType); return createMeasurementInfo(measurementProperties); } private String filter(String val, ProductPlugin productPlugin, TokenReplacer replacer) { return replacer.replaceTokens(val); } private String getObjectNameProperty(ObjectName objectName) { final StringBuffer objectNameProperty = new StringBuffer(objectName .getDomain()).append(':'); Hashtable keyProperties = objectName.getKeyPropertyList(); for (Iterator iterator = keyProperties.entrySet().iterator(); iterator .hasNext();) { Map.Entry keyProperty = (Map.Entry) iterator.next(); objectNameProperty.append(keyProperty.getKey()).append('='); // for now, recognize only type and subtype - replace all others // with variable placeholders if ("type".equals(keyProperty.getKey()) || "subtype".equals(keyProperty.getKey())) { objectNameProperty.append(keyProperty.getValue()); } else { objectNameProperty.append('%').append(keyProperty.getKey()) .append('%'); } objectNameProperty.append(','); } objectNameProperty.deleteCharAt(objectNameProperty.length() - 1); return objectNameProperty.toString(); } /** * Returns a ServiceType containing ONLY the properties needed at construction time (the ones that guarantee uniqueness) * @param productName The name of the product containing the service * @param serverType The name of the server containing the service * @param serviceInfo Info about the service * @param objectName The {@link ObjectName} of the discovered MBean representing the service instance * @return A ServiceType containing ONLY the properties needed at construction time (the ones that guarantee uniqueness) */ public ServiceType getServiceType(String productName, ServerTypeInfo serverType, ModelMBeanInfo serviceInfo, ObjectName objectName) { String serviceTypeName = objectName.getKeyProperty("type"); final String subType = objectName.getKeyProperty("subtype"); if (subType != null) { serviceTypeName = serviceTypeName + " " + subType; } try { Descriptor serviceDescriptor = serviceInfo.getMBeanDescriptor(); if ("false".equals(serviceDescriptor.getFieldValue("export"))) { return null; } String serviceType = (String) serviceDescriptor .getFieldValue("typeName"); if (serviceType != null) { serviceTypeName = serviceType; } } catch (Exception e) { log .warn("Error obtaining MBeanInfo descriptor field values. Default values will be used. Cause: " + e.getMessage()); } return new ServiceType(serviceTypeName, productName, new ServiceTypeInfo(serverType .getName() + ' ' + serviceTypeName, serviceInfo.getDescription(), serverType)); } private boolean isMetric(MBeanAttributeInfo attribute) { if (attribute instanceof ModelMBeanAttributeInfo) { String attributeType = (String) ((ModelMBeanAttributeInfo) attribute) .getDescriptor().getFieldValue("attributeType"); //attributeType not in Spring 3.0 @ManagedMetric. If it's there from instrumented 2.5.6, process it if(attributeType != null) { if ("Metric".equals(attributeType)) { return true; }else { return false; } } String metricType = (String) ((ModelMBeanAttributeInfo) attribute) .getDescriptor().getFieldValue("metricType"); //this field is required for metrics - assume it's not a metric if not present if(metricType != null) { return true; } } return false; } }