/*
* 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) [2004-2008], Hyperic, 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.server.session;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hyperic.hq.alerts.AlertDefinitionXmlParser;
import org.hyperic.hq.appdef.server.session.AppdefResourceType;
import org.hyperic.hq.appdef.server.session.CpropKey;
import org.hyperic.hq.appdef.shared.AppdefEntityID;
import org.hyperic.hq.appdef.shared.AppdefEntityNotFoundException;
import org.hyperic.hq.appdef.shared.AppdefEntityValue;
import org.hyperic.hq.appdef.shared.CPropManager;
import org.hyperic.hq.appdef.shared.PlatformManager;
import org.hyperic.hq.appdef.shared.ServerManager;
import org.hyperic.hq.appdef.shared.ServiceManager;
import org.hyperic.hq.authz.shared.PermissionException;
import org.hyperic.hq.common.NotFoundException;
import org.hyperic.hq.common.VetoException;
import org.hyperic.hq.common.server.session.Audit;
import org.hyperic.hq.common.shared.AuditManager;
import org.hyperic.hq.context.Bootstrap;
import org.hyperic.hq.events.EventConstants;
import org.hyperic.hq.events.shared.AlertDefinitionManager;
import org.hyperic.hq.events.shared.AlertDefinitionValue;
import org.hyperic.hq.measurement.server.session.MonitorableMeasurementInfo;
import org.hyperic.hq.measurement.server.session.MonitorableType;
import org.hyperic.hq.measurement.shared.TemplateManager;
import org.hyperic.hq.product.MeasurementInfo;
import org.hyperic.hq.product.PlatformTypeInfo;
import org.hyperic.hq.product.Plugin;
import org.hyperic.hq.product.PluginException;
import org.hyperic.hq.product.PluginInfo;
import org.hyperic.hq.product.PluginManager;
import org.hyperic.hq.product.PluginNotFoundException;
import org.hyperic.hq.product.PluginUpdater;
import org.hyperic.hq.product.ProductPlugin;
import org.hyperic.hq.product.ProductPluginManager;
import org.hyperic.hq.product.ServerTypeInfo;
import org.hyperic.hq.product.ServiceType;
import org.hyperic.hq.product.ServiceTypeInfo;
import org.hyperic.hq.product.TypeInfo;
import org.hyperic.hq.product.pluginxml.PluginData;
import org.hyperic.hq.product.shared.PluginValue;
import org.hyperic.hq.product.shared.ProductManager;
import org.hyperic.util.config.ConfigOption;
import org.hyperic.util.config.ConfigResponse;
import org.hyperic.util.config.ConfigSchema;
import org.hyperic.util.timer.StopWatch;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service("ProductManager")
public class ProductManagerImpl implements ProductManager {
private final Log log = LogFactory.getLog(ProductManagerImpl.class);
private final CPropManager cPropManager;
private final TemplateManager templateManager;
private final AuditManager auditManager;
private final PluginUpdater pluginUpdater = new PluginUpdater();
private static final String ALERT_DEFINITIONS_XML_FILE = "etc/alert-definitions.xml";
private final AlertDefinitionManager alertDefinitionManager;
private final PluginDAO pluginDao;
private final PlatformManager platformManager;
private final ServerManager serverManager;
private final ServiceManager serviceManager;
private final AlertDefinitionXmlParser alertDefinitionXmlParser;
private final PluginAuditFactory pluginAuditFactory;
@Autowired
public ProductManagerImpl(PluginDAO pluginDao, AlertDefinitionManager alertDefinitionManager,
CPropManager cPropManager, TemplateManager templateManager,
AuditManager auditManager, ServerManager serverManager,
ServiceManager serviceManager, PlatformManager platformManager,
AlertDefinitionXmlParser alertDefinitionXmlParser,
PluginAuditFactory pluginAuditFactory) {
this.pluginDao = pluginDao;
this.alertDefinitionManager = alertDefinitionManager;
this.cPropManager = cPropManager;
this.templateManager = templateManager;
this.auditManager = auditManager;
this.serverManager = serverManager;
this.serviceManager = serviceManager;
this.platformManager = platformManager;
this.alertDefinitionXmlParser = alertDefinitionXmlParser;
this.pluginAuditFactory = pluginAuditFactory;
}
/**
* Update the appdef entities based on TypeInfo
*/
private void updateAppdefEntities(String pluginName, TypeInfo[] entities) throws VetoException,
NotFoundException {
ArrayList<TypeInfo> platforms = new ArrayList<TypeInfo>();
ArrayList<TypeInfo> servers = new ArrayList<TypeInfo>();
ArrayList<TypeInfo> services = new ArrayList<TypeInfo>();
// Organize the entity infos first
for (int i = 0; i < entities.length; i++) {
TypeInfo ei = entities[i];
switch (ei.getType()) {
case TypeInfo.TYPE_PLATFORM:
platforms.add(ei);
break;
case TypeInfo.TYPE_SERVER:
servers.add(ei);
break;
case TypeInfo.TYPE_SERVICE:
services.add(ei);
break;
default:
break;
}
}
StopWatch watch = new StopWatch();
final boolean debug = log.isDebugEnabled();
// Update platforms
if (platforms.size() > 0) {
this.platformManager.updatePlatformTypes(pluginName, platforms
.toArray(new PlatformTypeInfo[0]));
}
// Update servers
if (servers.size() > 0) {
if (debug) watch.markTimeBegin("updateServerTypes");
serverManager.updateServerTypes(pluginName, servers.toArray(new ServerTypeInfo[0]));
if (debug) watch.markTimeEnd("updateServerTypes");
}
// Update services
if (services.size() > 0) {
if (debug) watch.markTimeBegin("updateServiceTypes");
serviceManager.updateServiceTypes(pluginName, services.toArray(new ServiceTypeInfo[0]));
if (debug) watch.markTimeEnd("updateServiceTypes");
}
if (debug) log.debug(watch);
}
private ProductPluginManager getProductPluginManager() {
return Bootstrap.getBean(ProductPluginDeployer.class).getProductPluginManager();
}
/**
*/
@Transactional(readOnly = true)
public TypeInfo getTypeInfo(AppdefEntityValue value) throws PermissionException,
AppdefEntityNotFoundException {
return getProductPluginManager().getTypeInfo(value.getBasePlatformName(),
value.getTypeName());
}
/**
*/
// TODO This is a get method, but the transaction cannot be read-only since
// modifications are made.
@Transactional
public PluginManager getPluginManager(String type) throws PluginException {
return getProductPluginManager().getPluginManager(type);
}
/**
*/
// TODO: G
@Transactional(readOnly = true)
public String getMonitoringHelp(AppdefEntityValue entityVal, Map<?, ?> props)
throws PluginNotFoundException, PermissionException, AppdefEntityNotFoundException {
TypeInfo info = getTypeInfo(entityVal);
String help = getProductPluginManager().getMeasurementPluginManager().getHelp(info, props);
if (help == null) {
return null;
}
return help;
}
/**
*/
@Transactional(readOnly = true)
public ConfigSchema getConfigSchema(String type, String name, AppdefEntityValue entityVal,
ConfigResponse baseResponse) throws PluginException,
AppdefEntityNotFoundException, PermissionException {
PluginManager manager = getPluginManager(type);
TypeInfo info = getTypeInfo(entityVal);
return manager.getConfigSchema(name, info, baseResponse);
}
private void updatePlugin(PluginDAO plHome, PluginInfo pInfo) {
Plugin plugin = plHome.findByName(pInfo.name);
if (plugin == null) {
plHome.create(pInfo.name, pInfo.version, pInfo.jar, pInfo.md5);
} else {
plugin.setModifiedTime(System.currentTimeMillis());
plugin.setPath(pInfo.jar);
plugin.setMD5(pInfo.md5);
plugin.setVersion(pInfo.version);
}
}
// e.g. in ~/.hq/plugin.properties
// hq.plugins.system.forceUpdate=true
private boolean forceUpdate(String plugin) {
String key = ProductPluginManager.getPropertyKey(plugin, "forceUpdate");
return "true".equals(getProductPluginManager().getProperties().getProperty(key));
}
private void pluginDeployed(PluginInfo pInfo) {
// there is 1 hq-plugin.xml descriptor per-plugin which
// contains metrics for all types supported by said plugin.
// caching prevents reading/parsing the file for each type.
// at this point we've got all the measurements for this plugin
// so flush the cache to save some memory.
// the file will be re-read/parsed when the plugin is redeployed.
PluginData.deployed(pInfo.resourceLoader);
}
private boolean isVirtualServer(TypeInfo type) {
if (type.getType() != TypeInfo.TYPE_SERVER) {
return false;
}
return ((ServerTypeInfo) type).isVirtual();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void deploymentNotify(String pluginName, File dir)
throws PluginNotFoundException, VetoException, NotFoundException {
ProductPlugin pplugin = (ProductPlugin) getProductPluginManager().getPlugin(pluginName);
boolean created = false;
long start = System.currentTimeMillis();
PluginInfo pInfo = getProductPluginManager().getPluginInfo(pluginName);
Plugin plugin = pluginDao.findByName(pluginName);
PluginValue pluginVal = plugin != null ? plugin.getPluginValue() : null;
if (pluginVal != null && pInfo.name.equals(pluginVal.getName()) &&
pInfo.md5.equals(pluginVal.getMD5())) {
log.info(pluginName + " plugin up to date (" + dir + ")");
if (forceUpdate(pluginName)) {
log.info(pluginName + " configured to force update");
} else {
pluginDeployed(pInfo);
return;
}
} else {
log.info(pluginName + " unknown -- registering (" + dir + ")");
created = (pluginVal == null);
}
// Get the Appdef entities
TypeInfo[] entities = pplugin.getTypes();
if (entities == null) {
log.info(pluginName + " does not define any resource types");
updatePlugin(pluginDao, pInfo);
if (created) {
pluginAuditFactory.deployAudit(pluginName, start, System.currentTimeMillis());
} else {
pluginAuditFactory.updateAudit(pluginName, start, System.currentTimeMillis());
}
return;
}
Audit audit;
boolean pushed = false;
if (created) {
audit = pluginAuditFactory.deployAudit(pluginName, start, start);
} else {
audit = pluginAuditFactory.updateAudit(pluginName, start, start);
}
try {
auditManager.pushContainer(audit);
pushed = true;
updatePlugin(pluginName);
} finally {
if (pushed) {
auditManager.popContainer(true);
}
}
}
/**
* @param pluginName The name of the product plugin
* @param serviceTypes The Set of {@link ServiceType}s to update
* @throws PluginNotFoundException
* @throws VetoException
* @throws NotFoundException
*/
@Transactional
public void updateDynamicServiceTypePlugin(String pluginName, Set<ServiceType> serviceTypes)
throws PluginNotFoundException, NotFoundException, VetoException {
ProductPlugin productPlugin = (ProductPlugin) getProductPluginManager().getPlugin(
pluginName);
try {
pluginUpdater.updateServiceTypes(productPlugin, serviceTypes);
updatePlugin(pluginName);
} catch (PluginException e) {
log.error("Error updating service types. Cause: " + e.getMessage());
}
}
private void updatePlugin(String pluginName)
throws VetoException, PluginNotFoundException, NotFoundException {
final boolean debug = log.isDebugEnabled();
final StopWatch watch = new StopWatch();
ProductPluginManager ppm = getProductPluginManager();
ProductPlugin pplugin = (ProductPlugin) ppm.getPlugin(pluginName);
PluginInfo pInfo = getProductPluginManager().getPluginInfo(pluginName);
TypeInfo[] entities = pplugin.getTypes();
if (debug) watch.markTimeBegin("updateAppdefEntities");
updateAppdefEntities(pluginName, entities);
if (debug) watch.markTimeEnd("updateAppdefEntities");
// Get the measurement templates
// Keep a list of templates to add
Map<MonitorableType,List<MonitorableMeasurementInfo>> toAdd =
new HashMap<MonitorableType,List<MonitorableMeasurementInfo>>();
Map<String, MonitorableType> types =
new HashMap<String,MonitorableType>(templateManager.getMonitorableTypesByName(pluginName));
if (debug) watch.markTimeBegin("loop0");
for (TypeInfo info : Arrays.asList(entities)) {
MeasurementInfo[] measurements;
try {
measurements = ppm.getMeasurementPluginManager().getMeasurements(info);
} catch (PluginNotFoundException e) {
if (!isVirtualServer(info)) {
log.info(info.getName() + " does not support measurement");
}
continue;
}
if (measurements != null && measurements.length > 0) {
if (debug)
watch.markTimeBegin("getMonitorableType");
MonitorableType monitorableType = types.get(info.getName());
if (monitorableType == null) {
monitorableType = templateManager.createMonitorableType(pluginName, info);
types.put(info.getName(), monitorableType);
}
if (debug)
watch.markTimeEnd("getMonitorableType");
if (debug)
watch.markTimeBegin("updateTemplates");
Map<String, MeasurementInfo> newMeasurements = templateManager.updateTemplates(
pluginName, info, monitorableType, measurements);
if (debug)
watch.markTimeEnd("updateTemplates");
final List<MonitorableMeasurementInfo> infos = new ArrayList<MonitorableMeasurementInfo>();
for(MeasurementInfo measurementInfo: newMeasurements.values()) {
infos.add(new MonitorableMeasurementInfo(monitorableType, measurementInfo));
}
//we may encounter the same set of templates twice. Last one wins
toAdd.put(monitorableType,infos);
}
}
if (debug) watch.markTimeEnd("loop0");
pluginDao.getSession().flush();
// For performance reasons, we add all the new measurements at once.
if (debug) watch.markTimeBegin("createTemplates");
templateManager.createTemplates(pluginName, toAdd);
if (debug) watch.markTimeEnd("createTemplates");
// Add any custom properties.
if (debug) watch.markTimeBegin("findResourceType");
Map<String, AppdefResourceType> rTypes =
cPropManager.findResourceType(Arrays.asList(entities));
if (debug) watch.markTimeEnd("findResourceType");
if (debug) watch.markTimeBegin("loop");
for (int i = 0; i < entities.length; i++) {
TypeInfo info = entities[i];
ConfigSchema schema = pplugin.getCustomPropertiesSchema(info);
List<ConfigOption> options = schema.getOptions();
AppdefResourceType appdefType = rTypes.get(info.getName());
for (ConfigOption opt : options) {
if (debug)
watch.markTimeBegin("findByKey");
CpropKey c = cPropManager.findByKey(appdefType, opt.getName());
if (debug)
watch.markTimeEnd("findByKey");
if (c == null) {
cPropManager.addKey(appdefType, opt.getName(), opt.getDescription());
}
}
}
if (debug) watch.markTimeEnd("loop");
createAlertDefinitions(pInfo);
pluginDeployed(pInfo);
updatePlugin(pluginDao, pInfo);
if (debug) log.debug(watch);
}
private void createAlertDefinitions(final PluginInfo pInfo) throws VetoException {
final InputStream alertDefns = pInfo.resourceLoader
.getResourceAsStream(ALERT_DEFINITIONS_XML_FILE);
if (alertDefns == null) {
return;
}
try {
final Set<AlertDefinitionValue> alertDefs = alertDefinitionXmlParser.parse(alertDefns);
for (AlertDefinitionValue alertDefinition : alertDefs) {
try {
final AppdefEntityID id = new AppdefEntityID(alertDefinition.getAppdefType(),
alertDefinition.getAppdefId());
final SortedMap<String, Integer> existingAlertDefinitions = alertDefinitionManager
.findAlertDefinitionNames(id, EventConstants.TYPE_ALERT_DEF_ID);
// TODO update existing alert defs - for now, just create if
// one does not exist. Be aware that this method is also
// called
// when new service type metadata is discovered (from
// updateServiceTypes method), as well as when a new or
// modified plugin jar is detected
if (!(existingAlertDefinitions.keySet().contains(alertDefinition.getName()))) {
alertDefinitionManager.createAlertDefinition(alertDefinition);
}
} catch (Exception e) {
log.error("Unable to load some or all of alert definitions for plugin " +
pInfo.name + ". Cause: " + e.getMessage());
}
}
} catch (Exception e) {
log.error("Unable to parse alert definitions for plugin " + pInfo.name + ". Cause: " +
e.getMessage());
} finally {
try {
alertDefns.close();
} catch (IOException e) {
log.warn("Error closing InputStream to alert definitions file of plugin " +
pInfo.name + ". Cause: " + e.getMessage());
}
}
}
}