/*
* RHQ Management Platform
* Copyright (C) 2005-2011 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 as published by
* the Free Software Foundation version 2 of the License.
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.enterprise.server.sync.importers;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.XMLStreamException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.rhq.core.clientapi.agent.configuration.ConfigurationUtility;
import org.rhq.core.domain.auth.Subject;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.Property;
import org.rhq.core.domain.configuration.PropertyList;
import org.rhq.core.domain.configuration.PropertyMap;
import org.rhq.core.domain.configuration.PropertySimple;
import org.rhq.core.domain.configuration.definition.ConfigurationDefinition;
import org.rhq.core.domain.configuration.definition.PropertyDefinitionList;
import org.rhq.core.domain.configuration.definition.PropertyDefinitionMap;
import org.rhq.core.domain.configuration.definition.PropertyDefinitionSimple;
import org.rhq.core.domain.configuration.definition.PropertySimpleType;
import org.rhq.core.domain.measurement.MeasurementDefinition;
import org.rhq.core.domain.sync.entity.MetricTemplate;
import org.rhq.enterprise.server.measurement.MeasurementScheduleManagerLocal;
import org.rhq.enterprise.server.sync.ExportReader;
import org.rhq.enterprise.server.sync.validators.EntityValidator;
import org.rhq.enterprise.server.sync.validators.UniquenessValidator;
import org.rhq.enterprise.server.util.LookupUtil;
/**
*
*
* @author Lukas Krejci
*/
public class MetricTemplateImporter implements Importer<MeasurementDefinition, MetricTemplate> {
private static final Log LOG = LogFactory.getLog(MetricTemplateImporter.class);
private static final boolean UPDATE_SCHEDULES_DEFAULT = false;
public static final String UPDATE_ALL_SCHEDULES_PROPERTY = "updateAllSchedules";
public static final String METRIC_NAME_PROPERTY = "metricName";
public static final String RESOURCE_TYPE_NAME_PROPERTY = "resourceTypeName";
public static final String RESOURCE_TYPE_PLUGIN_PROPERTY = "resourceTypePlugin";
public static final String UPDATE_SCHEDULES_PROPERTY = "updateSchedules";
public static final String METRIC_UPDATE_OVERRIDES_PROPERTY = "metricUpdateOverrides";
public static final String METRIC_UPDATE_OVERRIDE_PROPERTY = "metricUpdateOverride";
private static final String IMPORT_NOTES_PROLOGUE = "The following metric templates were not imported because they do not correspond to any metric defined by the existing resource types:\n";
private static class UpdateKey {
public final long collectionInterval;
public final boolean updateSchedules;
public final boolean enable;
public UpdateKey(long collectionInterval, boolean updateSchedules, boolean enable) {
this.collectionInterval = collectionInterval;
this.updateSchedules = updateSchedules;
this.enable = enable;
}
@Override
public int hashCode() {
return (int) collectionInterval * (updateSchedules ? 31 : 1) * (enable ? 31 : 1);
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof UpdateKey)) {
return false;
}
UpdateKey o = (UpdateKey) other;
return o.collectionInterval == collectionInterval && o.updateSchedules == updateSchedules
&& enable == o.enable;
}
}
private static final Comparator<MetricTemplate> METRIC_TEMPLATE_COMPARATOR = new Comparator<MetricTemplate>() {
@Override
public int compare(MetricTemplate o1, MetricTemplate o2) {
//sort by plugin, type name, and metric name in that order
int ret = o1.getResourceTypePlugin().compareTo(o2.getResourceTypePlugin());
if (ret != 0) {
return ret;
}
ret = o1.getResourceTypeName().compareTo(o2.getResourceTypeName());
if (ret != 0) {
return ret;
}
return o1.getMetricName().compareTo(o2.getMetricName());
}
};
private Subject subject;
private EntityManager entityManager;
private Map<UpdateKey, List<MeasurementDefinition>> definitionsByUpdateKey =
new HashMap<UpdateKey, List<MeasurementDefinition>>();
private Configuration importConfiguration;
private Unmarshaller unmarshaller;
private MeasurementScheduleManagerLocal measurementScheduleManager;
private Set<MetricTemplate> unmatchedTemplates = new TreeSet<MetricTemplate>(METRIC_TEMPLATE_COMPARATOR);
public MetricTemplateImporter(Subject subject, EntityManager entityManager) {
this(subject, entityManager, LookupUtil.getMeasurementScheduleManager());
}
public MetricTemplateImporter(Subject subject, EntityManager entityManager,
MeasurementScheduleManagerLocal measurementScheduleManager) {
try {
this.subject = subject;
this.entityManager = entityManager;
JAXBContext context = JAXBContext.newInstance(MetricTemplate.class);
unmarshaller = context.createUnmarshaller();
} catch (JAXBException e) {
throw new IllegalStateException("Failed to initialize JAXB marshaller for MetricTemplate.", e);
}
this.measurementScheduleManager = measurementScheduleManager;
}
@Override
public ConfigurationDefinition getImportConfigurationDefinition() {
ConfigurationDefinition def = new ConfigurationDefinition("MetricTemplateImportConfiguration", null);
PropertyDefinitionSimple updateAllSchedules =
new PropertyDefinitionSimple(
UPDATE_ALL_SCHEDULES_PROPERTY,
"If set to true, all the metric templates will update all the existing schedules on corresponding resources.",
true, PropertySimpleType.BOOLEAN);
updateAllSchedules.setDefaultValue(Boolean.toString(UPDATE_SCHEDULES_DEFAULT));
def.put(updateAllSchedules);
PropertyDefinitionSimple metricName =
new PropertyDefinitionSimple(METRIC_NAME_PROPERTY, "The name of the metric", true,
PropertySimpleType.STRING);
PropertyDefinitionSimple resourceTypeName =
new PropertyDefinitionSimple(RESOURCE_TYPE_NAME_PROPERTY,
"The name of the resource type defining the metric", true, PropertySimpleType.STRING);
PropertyDefinitionSimple resourceTypePlugin =
new PropertyDefinitionSimple(RESOURCE_TYPE_PLUGIN_PROPERTY,
"The name of the plugin defining the resource type that defines the metric", true,
PropertySimpleType.STRING);
PropertyDefinitionSimple updateSchedules =
new PropertyDefinitionSimple(UPDATE_SCHEDULES_PROPERTY,
"Whether to update the schedules of this metric on existing resources", true,
PropertySimpleType.BOOLEAN);
PropertyDefinitionMap metricUpdateOverride =
new PropertyDefinitionMap(METRIC_UPDATE_OVERRIDE_PROPERTY, null, true, metricName, resourceTypeName,
resourceTypePlugin, updateSchedules);
PropertyDefinitionList metricUpdateOverrides =
new PropertyDefinitionList(METRIC_UPDATE_OVERRIDES_PROPERTY, "Per metric settings", false,
metricUpdateOverride);
def.put(metricUpdateOverrides);
ConfigurationUtility.initializeDefaultTemplate(def);
return def;
}
@Override
public void configure(Configuration configuration) {
this.importConfiguration = configuration;
}
@Override
public ExportedEntityMatcher<MeasurementDefinition, MetricTemplate> getExportedEntityMatcher() {
return new ExportedEntityMatcher<MeasurementDefinition, MetricTemplate>() {
private Map<MetricTemplate, MeasurementDefinition> cache;
{
//this instance will be used many many times to find the measurement
//definitions that correspond to the templates stored in the export file
//it is therefore more optimal to just preload all the mds in a cache
//and use that to find matches than to query the database each time.
cache = new HashMap<MetricTemplate, MeasurementDefinition>();
Query q = entityManager.createQuery("SELECT md FROM MeasurementDefinition md");
for (Object r : q.getResultList()) {
MeasurementDefinition md = (MeasurementDefinition) r;
cache.put(new MetricTemplate(md), md);
}
}
@Override
public MeasurementDefinition findMatch(MetricTemplate object) {
MeasurementDefinition md = cache.get(object);
if (md == null && LOG.isDebugEnabled()) {
LOG.debug("Failed to find a measurement definition corresponding to "
+ object
+ ". This means that the plugins in the source RHQ install were different than in this RHQ install "
+ "but the DeployedAgentPluginsValidator failed to catch that. This most probably means that the "
+ "export file has been tampered with. Letting the import continue because that might have been intentional change by the user.");
}
return md;
}
};
}
@Override
public Set<EntityValidator<MetricTemplate>> getEntityValidators() {
return Collections.<EntityValidator<MetricTemplate>>singleton(new UniquenessValidator<MetricTemplate>());
}
@Override
public void update(MeasurementDefinition entity, MetricTemplate exportedEntity) {
if (entity == null) {
unmatchedTemplates.add(exportedEntity);
return;
}
addToUpdateMap(exportedEntity, entity);
}
@Override
public MetricTemplate unmarshallExportedEntity(ExportReader reader) throws XMLStreamException {
try {
return (MetricTemplate) unmarshaller.unmarshal(reader);
} catch (JAXBException e) {
throw new XMLStreamException("Failed to unmarshal metric template.", e);
}
}
@Override
public String finishImport() {
for (Map.Entry<UpdateKey, List<MeasurementDefinition>> e : definitionsByUpdateKey.entrySet()) {
int[] ids = getIdsFromDefs(e.getValue());
boolean enable = e.getKey().enable;
boolean updateSchedules = e.getKey().updateSchedules;
long collectionInterval = e.getKey().collectionInterval;
measurementScheduleManager.updateDefaultCollectionIntervalAndEnablementForMeasurementDefinitions(subject,
ids, collectionInterval, enable, updateSchedules);
}
if (unmatchedTemplates.isEmpty()) {
return null;
} else {
return getUnmatchedMetricTemplatesReport(unmatchedTemplates);
}
}
//public for testability
public static String getUnmatchedMetricTemplatesReport(Set<MetricTemplate> metricTemplates) {
StringBuilder bld = new StringBuilder(IMPORT_NOTES_PROLOGUE);
for(MetricTemplate t : metricTemplates) {
bld.append(t).append("\n");
}
return bld.toString();
}
private static int[] getIdsFromDefs(Collection<MeasurementDefinition> defs) {
int[] ids = new int[defs.size()];
int i = 0;
for (MeasurementDefinition d : defs) {
ids[i++] = d.getId();
}
return ids;
}
private void addToUpdateMap(MetricTemplate metricTemplate, MeasurementDefinition def) {
UpdateKey key = new UpdateKey(metricTemplate.getDefaultInterval(), shouldUpdateSchedules(metricTemplate), metricTemplate.isEnabled());
List<MeasurementDefinition> defs = definitionsByUpdateKey.get(key);
if (defs == null) {
defs = new ArrayList<MeasurementDefinition>();
definitionsByUpdateKey.put(key, defs);
}
defs.add(def);
}
private boolean shouldUpdateSchedules(MetricTemplate def) {
if (importConfiguration == null) {
return UPDATE_SCHEDULES_DEFAULT;
}
String updateAll = importConfiguration.getSimpleValue(UPDATE_ALL_SCHEDULES_PROPERTY, null);
if (updateAll != null) {
if (Boolean.parseBoolean(updateAll)) {
return true;
}
}
PropertyList perMetricOverrides = importConfiguration.getList(METRIC_UPDATE_OVERRIDES_PROPERTY);
if (perMetricOverrides == null) {
return UPDATE_SCHEDULES_DEFAULT;
}
PropertySimple override = findOverrideForMetric(def, perMetricOverrides);
if (override == null) {
return UPDATE_SCHEDULES_DEFAULT;
}
return override.getBooleanValue();
}
PropertySimple findOverrideForMetric(MetricTemplate def, PropertyList overrides) {
for (Property p : overrides.getList()) {
PropertyMap map = (PropertyMap) p;
String metricName = map.getSimpleValue(METRIC_NAME_PROPERTY, null);
String resourceTypeName = map.getSimpleValue(RESOURCE_TYPE_NAME_PROPERTY, null);
String resourceTypePlugin = map.getSimpleValue(RESOURCE_TYPE_PLUGIN_PROPERTY, null);
PropertySimple updateSchedules = map.getSimple(UPDATE_SCHEDULES_PROPERTY);
if (metricName == null || resourceTypeName == null || resourceTypePlugin == null || updateSchedules == null) {
continue;
}
if (metricName.equals(def.getMetricName()) && resourceTypeName.equals(def.getResourceTypeName())
&& resourceTypePlugin.equals(def.getResourceTypePlugin())) {
return updateSchedules;
}
}
return null;
}
}