/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.ambari.server.state.alert; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.Reader; import java.lang.reflect.Type; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.UUID; import org.apache.ambari.server.AmbariException; import org.apache.ambari.server.orm.entities.AlertDefinitionEntity; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.reflect.TypeToken; import com.google.inject.Singleton; /** * The {@link AlertDefinitionFactory} class is used to construct * {@link AlertDefinition} instances from a variety of sources. */ @Singleton public class AlertDefinitionFactory { /** * Logger. */ private final static Logger LOG = LoggerFactory.getLogger(AlertDefinitionFactory.class); /** * Builder used for type adapter registration. */ private final GsonBuilder m_builder = new GsonBuilder(); /** * Thread safe deserializer. */ private final Gson m_gson; /** * Constructor. */ public AlertDefinitionFactory() { m_builder.registerTypeAdapter(Source.class, new AlertDefinitionSourceAdapter()); m_gson = m_builder.create(); } /** * Gets a list of all of the alert definitions defined in the specified JSON * {@link File} for the given service. Each of the JSON files should have a * mapping between the service and the alerts defined for that service. This * is necessary since some services are combined in a single * {@code metainfo.xml} and only have a single directory on the stack. * * @param alertDefinitionFile * the JSON file from the stack to read (not {@code null}). * @param serviceName * the name of the service to extract definitions for (not * {@code null}). * @return the definitions for the specified service, or an empty set. * @throws AmbariException * if there was a problem reading the file or parsing the JSON. */ public Set<AlertDefinition> getAlertDefinitions(File alertDefinitionFile, String serviceName) throws AmbariException { try { FileReader fileReader = new FileReader(alertDefinitionFile); return getAlertDefinitions(fileReader, serviceName); } catch (IOException ioe) { String message = "Could not read the alert definition file"; LOG.error(message, ioe); throw new AmbariException(message, ioe); } } /** * Gets a list of all of the alert definitions defined in the resource pointed * to by the specified reader for the given service. There should have a * mapping between the service and the alerts defined for that service. This * is necessary since some services are combined in a single * {@code metainfo.xml} and only have a single directory on the stack. * <p/> * The supplied reader is closed when this method completes. * * @param reader * the reader to read from (not {@code null}). This will be closed * after reading is done. * @param serviceName * the name of the service to extract definitions for (not * {@code null}). * @return the definitions for the specified service, or an empty set. * @throws AmbariException * if there was a problem reading or parsing the JSON. */ public Set<AlertDefinition> getAlertDefinitions(Reader reader, String serviceName) throws AmbariException { // { MAPR : {definitions}, YARN : {definitions} } Map<String, Map<String, List<AlertDefinition>>> serviceDefinitionMap = null; try { Type type = new TypeToken<Map<String, Map<String, List<AlertDefinition>>>>() {}.getType(); serviceDefinitionMap = m_gson.fromJson(reader, type); } catch (Exception e) { LOG.error("Could not read the alert definitions", e); throw new AmbariException("Could not read alert definitions", e); } finally { IOUtils.closeQuietly(reader); } Set<AlertDefinition> definitions = new HashSet<>(); // it's OK if the service doesn't have any definitions; this can happen if // 2 services are defined in a single metainfo.xml and only 1 service has // alerts defined Map<String, List<AlertDefinition>> definitionMap = serviceDefinitionMap.get(serviceName); if (null == definitionMap) { return definitions; } for (Entry<String, List<AlertDefinition>> entry : definitionMap.entrySet()) { for (AlertDefinition ad : entry.getValue()) { ad.setServiceName(serviceName); if (!entry.getKey().equals("service")) { ad.setComponentName(entry.getKey()); } } definitions.addAll(entry.getValue()); } return definitions; } /** * Gets an {@link AlertDefinition} constructed from the specified * {@link AlertDefinitionEntity}. * * @param entity * the entity to use to construct the {@link AlertDefinition} (not * {@code null}). * @return the definiion or {@code null} if it could not be coerced. */ public AlertDefinition coerce(AlertDefinitionEntity entity) { if (null == entity) { return null; } AlertDefinition definition = new AlertDefinition(); definition.setClusterId(entity.getClusterId()); definition.setDefinitionId(entity.getDefinitionId()); definition.setComponentName(entity.getComponentName()); definition.setEnabled(entity.getEnabled()); definition.setHostIgnored(entity.isHostIgnored()); definition.setInterval(entity.getScheduleInterval()); definition.setName(entity.getDefinitionName()); definition.setScope(entity.getScope()); definition.setServiceName(entity.getServiceName()); definition.setLabel(entity.getLabel()); definition.setHelpURL(entity.getHelpURL()); definition.setDescription(entity.getDescription()); definition.setUuid(entity.getHash()); try{ String sourceJson = entity.getSource(); Source source = m_gson.fromJson(sourceJson, Source.class); definition.setSource(source); } catch (Exception exception) { LOG.error( "Unable to deserialize the alert definition source during coercion", exception); return null; } return definition; } /** * Gets an {@link AlertDefinitionEntity} constructed from the specified * {@link AlertDefinition}. * <p/> * The new entity will have a UUID already set. * * @param clusterId * the ID of the cluster. * @param definition * the definition to use to construct the * {@link AlertDefinitionEntity} (not {@code null}). * @return the definiion or {@code null} if it could not be coerced. */ public AlertDefinitionEntity coerce(long clusterId, AlertDefinition definition) { if (null == definition) { return null; } AlertDefinitionEntity entity = new AlertDefinitionEntity(); entity.setClusterId(clusterId); return merge(definition, entity); } /** * Merges the specified {@link AlertDefinition} into the * {@link AlertDefinitionEntity}, leaving any fields not merged intact. * <p/> * The merged entity will have a new UUID. * * @param definition * the definition to merge into the entity (not {@code null}). * @param entity * the entity to merge into (not {@code null}). * @return a merged, but not yes persisted entity, or {@code null} if the * merge could not be done. */ public AlertDefinitionEntity merge(AlertDefinition definition, AlertDefinitionEntity entity) { entity.setComponentName(definition.getComponentName()); entity.setDefinitionName(definition.getName()); entity.setEnabled(definition.isEnabled()); entity.setHostIgnored(definition.isHostIgnored()); entity.setHash(UUID.randomUUID().toString()); entity.setLabel(definition.getLabel()); entity.setDescription(definition.getDescription()); entity.setScheduleInterval(definition.getInterval()); entity.setHelpURL(definition.getHelpURL()); entity.setServiceName(definition.getServiceName()); Scope scope = definition.getScope(); if (null == scope) { scope = Scope.ANY; } entity.setScope(scope); Source source = definition.getSource(); entity.setSourceType(source.getType()); try { String sourceJson = m_gson.toJson(source); entity.setSource(sourceJson); } catch (Exception exception) { LOG.error( "Unable to serialize the alert definition source during coercion", exception); return null; } return entity; } /** * Gets an instance of {@link Gson} that can correctly serialize and * deserialize an {@link AlertDefinition}. * * @return a {@link Gson} instance (not {@code null}). */ public Gson getGson() { return m_gson; } /** * Deserializes {@link Source} implementations. */ private static final class AlertDefinitionSourceAdapter implements JsonDeserializer<Source>{ /** * */ @Override public Source deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { JsonObject jsonObj = (JsonObject) json; SourceType type = SourceType.valueOf(jsonObj.get("type").getAsString()); Class<? extends Source> clazz = null; switch (type) { case METRIC:{ clazz = MetricSource.class; break; } case AMS:{ clazz = AmsSource.class; break; } case PORT:{ clazz = PortSource.class; break; } case SCRIPT: { clazz = ScriptSource.class; break; } case AGGREGATE: { clazz = AggregateSource.class; break; } case PERCENT: { clazz = PercentSource.class; break; } case WEB: { clazz = WebSource.class; break; } case RECOVERY: { clazz = RecoverySource.class; break; } case SERVER:{ clazz = ServerSource.class; break; } default: break; } if (null == clazz) { LOG.warn( "Unable to deserialize an alert definition with source type {}", type); return null; } return context.deserialize(json, clazz); } } }