/*
* RHQ Management Platform
* Copyright (C) 2005-2008 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2, as
* published by the Free Software Foundation, and/or the GNU Lesser
* General Public License, version 2.1, also 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 and the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* and the GNU Lesser General Public License along with this program;
* if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.rhq.plugins.jmx;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mc4j.ems.connection.EmsConnection;
import org.mc4j.ems.connection.bean.EmsBean;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.PropertySimple;
import org.rhq.core.domain.resource.ResourceType;
import org.rhq.core.pluginapi.inventory.DiscoveredResourceDetails;
import org.rhq.core.pluginapi.inventory.ResourceDiscoveryComponent;
import org.rhq.core.pluginapi.inventory.ResourceDiscoveryContext;
import org.rhq.plugins.jmx.util.ObjectNameQueryUtility;
/**
* This is meant to be a generic discovery component for MBeans. In order to use it you configure your resource
* descriptor to use this class for discovery and then provide a plugin configuration default value for the "objectName"
* property that has a default value of the form defined by {@see ObjectNameQueryUtility}. Also, setting default
* configurations values for nameTemplate and descriptionTemplate according to the message format also defined by
* {@see ObjectNameQueryUtility} also lets you customize the detected resources name and description according to a
* variable replacement strategy. Additionally, any mapped variables found will be set as configuration properties in
* their own right.
*
* @author Greg Hinkle
*/
public class MBeanResourceDiscoveryComponent<T extends JMXComponent<?>> implements ResourceDiscoveryComponent<T> {
// Constants --------------------------------------------
/**
* Required when using this discovery component. The value of this property is the query for retrieving the MBeans.
*/
public static final String PROPERTY_OBJECT_NAME = "objectName";
/**
* Not required when using this discovery component. If this is specified, it will be used in the generation of the
* resource name. Any variables specified for replacement (via the {name} syntax) will be substituted prior to
* setting the resource name. Through this mechanism, unique names can be created for each resource discovered of
* the same resource type.
*/
public static final String PROPERTY_NAME_TEMPLATE = "nameTemplate";
/**
* Not required when using this discovery component. Similar to {@link #PROPERTY_NAME_TEMPLATE}, unique descriptions
* can be generated per resource using this template.
*/
public static final String PROPERTY_DESCRIPTION_TEMPLATE = "descriptionTemplate";
// Attributes --------------------------------------------
private final Log log = LogFactory.getLog(this.getClass());
protected ResourceDiscoveryContext<T> discoveryContext;
// ResourceDiscoveryComponent Implementation --------------------------------------------
public Set<DiscoveredResourceDetails> discoverResources(ResourceDiscoveryContext<T> context) {
return discoverResources(context, true);
}
// Public --------------------------------------------
/**
* Same as {@link #discoverResources(ResourceDiscoveryContext<T>)} with additional param.
*
* @param skipUnknownProps if true, skip over MBeans that have unknown properties in their ObjectName
*/
public Set<DiscoveredResourceDetails> discoverResources(ResourceDiscoveryContext<T> context,
boolean skipUnknownProps) {
this.discoveryContext = context;
return performDiscovery(context.getDefaultPluginConfiguration(), context.getParentResourceComponent(),
context.getResourceType(), skipUnknownProps);
}
/**
* Performs the actual discovery through MBeans.
*
* @param pluginConfiguration plugin configuration for the resource type being discovered; used to grab values
* that govern the MBean query and resource details generation
* @param parentResourceComponent parent resource of the resource being discovered
* @param resourceType type of resource being discovered
* @return set describing the resources discovered; empty set if no resources are found
*/
public Set<DiscoveredResourceDetails> performDiscovery(Configuration pluginConfiguration,
JMXComponent parentResourceComponent, ResourceType resourceType) {
return performDiscovery(pluginConfiguration, parentResourceComponent, resourceType, true);
}
/**
* Performs the actual discovery through MBeans.
*
* @param pluginConfiguration plugin configuration for the resource type being discovered; used to grab values
* that govern the MBean query and resource details generation
* @param parentResourceComponent parent resource of the resource being discovered
* @param resourceType type of resource being discovered
* @param skipUnknownProps Should we skip over MBeans that have unknown properties in their ObjectName
* @return set describing the resources discovered; empty set if no resources are found
*/
public Set<DiscoveredResourceDetails> performDiscovery(Configuration pluginConfiguration,
JMXComponent parentResourceComponent, ResourceType resourceType, boolean skipUnknownProps) {
String objectNameQueryTemplateOrig = pluginConfiguration.getSimple(PROPERTY_OBJECT_NAME).getStringValue();
log.debug("Discovering MBean resources with object name query template: " + objectNameQueryTemplateOrig);
EmsConnection connection = parentResourceComponent.getEmsConnection();
if (connection == null) {
throw new NullPointerException("The parent resource component [" + parentResourceComponent
+ "] returned a null connection - cannot discover MBeans without a connection");
}
Set<DiscoveredResourceDetails> services = new HashSet<DiscoveredResourceDetails>();
String templates[] = objectNameQueryTemplateOrig.split("\\|");
for (String objectNameQueryTemplate : templates) {
// Get the query template, replacing the parent key variables with the values from the parent configuration
ObjectNameQueryUtility queryUtility = new ObjectNameQueryUtility(objectNameQueryTemplate,
(this.discoveryContext != null) ? this.discoveryContext.getParentResourceContext()
.getPluginConfiguration() : null);
List<EmsBean> beans = connection.queryBeans(queryUtility.getTranslatedQuery());
if (log.isDebugEnabled()) {
log.debug("Found [" + beans.size() + "] mbeans for query [" + queryUtility.getTranslatedQuery() + "].");
}
for (EmsBean bean : beans) {
if (queryUtility.setMatchedKeyValues(bean.getBeanName().getKeyProperties())) {
// Only use beans that have all the properties we've made variables of
// Don't match beans that have unexpected properties
if (skipUnknownProps
&& queryUtility.isContainsExtraKeyProperties(bean.getBeanName().getKeyProperties().keySet())) {
continue;
}
String resourceKey = bean.getBeanName().getCanonicalName(); // The detected object name
String nameTemplate = (pluginConfiguration.getSimple(PROPERTY_NAME_TEMPLATE) != null) ? pluginConfiguration
.getSimple(PROPERTY_NAME_TEMPLATE).getStringValue() : null;
String descriptionTemplate = (pluginConfiguration.getSimple(PROPERTY_DESCRIPTION_TEMPLATE) != null) ? pluginConfiguration
.getSimple(PROPERTY_DESCRIPTION_TEMPLATE).getStringValue() : null;
String name = resourceKey;
if (nameTemplate != null) {
name = queryUtility.formatMessage(nameTemplate);
}
String description = null;
if (descriptionTemplate != null) {
description = queryUtility.formatMessage(descriptionTemplate);
}
DiscoveredResourceDetails service = new DiscoveredResourceDetails(resourceType, resourceKey, name,
"", description, null, null);
Configuration config = service.getPluginConfiguration();
config.put(new PropertySimple(PROPERTY_OBJECT_NAME, bean.getBeanName().toString()));
Map<String, String> mappedVariableValues = queryUtility.getVariableValues();
for (String key : mappedVariableValues.keySet()) {
config.put(new PropertySimple(key, mappedVariableValues.get(key)));
}
services.add(service);
// Clear out the variables for the next bean detected
queryUtility.resetVariables();
}
}
if (log.isDebugEnabled()) {
log.debug("[" + services.size() + "] services have been added");
}
}
return services;
}
/**
* Loads the bean with the given object name.
*
* Subclasses are free to override this method in order to load the bean.
*
* @param objectName the name of the bean to load
* @return the bean that is loaded
*/
protected EmsBean loadBean(T context, String objectName) {
EmsConnection emsConnection = context.getEmsConnection();
if (emsConnection != null) {
EmsBean bean = emsConnection.getBean(objectName);
if (bean == null) {
// In some cases, this resource component may have been discovered by some means other than querying its
// parent's EMSConnection (e.g. ApplicationDiscoveryComponent uses a filesystem to discover EARs and
// WARs that are not yet deployed). In such cases, getBean() will return null, since EMS won't have the
// bean in its cache. To cover such cases, make an attempt to query the underlying MBeanServer for the
// bean before giving up.
emsConnection.queryBeans(objectName);
bean = emsConnection.getBean(objectName);
}
return bean;
}
return null;
}
}