io.gravitee.rest.api.service.impl.AlertServiceImpl Maven / Gradle / Ivy
/**
* Copyright (C) 2015 The Gravitee team (http://gravitee.io)
*
* Licensed 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 io.gravitee.rest.api.service.impl;
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.gravitee.alert.api.condition.Filter;
import io.gravitee.alert.api.condition.StringCondition;
import io.gravitee.alert.api.trigger.Trigger;
import io.gravitee.alert.api.trigger.TriggerProvider;
import io.gravitee.alert.api.trigger.command.AlertNotificationCommand;
import io.gravitee.alert.api.trigger.command.Command;
import io.gravitee.alert.api.trigger.command.Handler;
import io.gravitee.alert.api.trigger.command.ResolvePropertyCommand;
import io.gravitee.common.data.domain.Page;
import io.gravitee.common.utils.UUID;
import io.gravitee.notifier.api.Notification;
import io.gravitee.plugin.alert.AlertTriggerProviderManager;
import io.gravitee.repository.exceptions.TechnicalException;
import io.gravitee.repository.management.api.AlertEventRepository;
import io.gravitee.repository.management.api.AlertTriggerRepository;
import io.gravitee.repository.management.api.ApiRepository;
import io.gravitee.repository.management.api.search.AlertEventCriteria;
import io.gravitee.repository.management.api.search.builder.PageableBuilder;
import io.gravitee.repository.management.model.*;
import io.gravitee.rest.api.model.*;
import io.gravitee.rest.api.model.alert.*;
import io.gravitee.rest.api.model.api.ApiEntity;
import io.gravitee.rest.api.model.parameters.Key;
import io.gravitee.rest.api.model.parameters.ParameterReferenceType;
import io.gravitee.rest.api.service.*;
import io.gravitee.rest.api.service.exceptions.*;
import io.gravitee.rest.api.service.impl.alert.EmailNotifierConfiguration;
import java.io.IOException;
import java.time.Duration;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.stereotype.Component;
/**
* @author Azize ELAMRANI (azize at graviteesource.com)
* @author GraviteeSource Team
*/
@Component
public class AlertServiceImpl extends TransactionalService implements AlertService, InitializingBean {
private final Logger LOGGER = LoggerFactory.getLogger(AlertServiceImpl.class);
private static final String UNKNOWN_SERVICE = "1";
private static final String FIELD_API = "api";
private static final String FIELD_APPLICATION = "application";
private static final String FIELD_TENANT = "tenant";
private static final String FIELD_PLAN = "plan";
private static final String METADATA_NAME = "name";
private static final String METADATA_DELETED = "deleted";
private static final String METADATA_UNKNOWN = "unknown";
private static final String METADATA_VERSION = "version";
private static final String METADATA_UNKNOWN_API_NAME = "Unknown API (not found)";
private static final String METADATA_UNKNOWN_APPLICATION_NAME = "Unknown application (keyless)";
private static final String METADATA_DELETED_API_NAME = "Deleted API";
private static final String METADATA_DELETED_APPLICATION_NAME = "Deleted application";
private static final String METADATA_DELETED_TENANT_NAME = "Deleted tenant";
private static final String METADATA_DELETED_PLAN_NAME = "Deleted plan";
@Value("${notifiers.email.subject:[Gravitee.io] %s}")
private String subject;
@Value("${notifiers.email.host:#{null}}")
private String host;
@Value("${notifiers.email.port}")
private String port;
@Value("${notifiers.email.username:#{null}}")
private String username;
@Value("${notifiers.email.password:#{null}}")
private String password;
@Value("${notifiers.email.starttls.enabled:false}")
private boolean startTLSEnabled;
@Value("${notifiers.email.ssl.trustAll:false}")
private boolean sslTrustAll;
@Value("${notifiers.email.ssl.keyStore:#{null}}")
private String sslKeyStore;
@Value("${notifiers.email.ssl.keyStorePassword:#{null}}")
private String sslKeyStorePassword;
@Autowired
private ConfigurableEnvironment environment;
@Autowired
private ObjectMapper mapper;
@Autowired
private AlertTriggerRepository alertTriggerRepository;
@Autowired
private ApiService apiService;
@Autowired
private ApplicationService applicationService;
@Autowired
private PlanService planService;
@Autowired
private AlertEventRepository alertEventRepository;
@Autowired
private TriggerProvider triggerProvider;
@Autowired
private AlertTriggerProviderManager triggerProviderManager;
@Autowired
private ParameterService parameterService;
@Autowired
private ApiRepository apiRepository;
@Autowired
private ApiMetadataService apiMetadataService;
@Override
public AlertStatusEntity getStatus() {
AlertStatusEntity status = new AlertStatusEntity();
status.setEnabled(parameterService.findAsBoolean(Key.ALERT_ENABLED, ParameterReferenceType.ORGANIZATION));
status.setPlugins(triggerProviderManager.findAll().size());
return status;
}
@Override
public AlertTriggerEntity create(final NewAlertTriggerEntity newAlertTrigger) {
checkAlert();
try {
// Get trigger
AlertTrigger alertTrigger = convert(newAlertTrigger);
alertTrigger.setCreatedAt(new Date());
alertTrigger.setUpdatedAt(alertTrigger.getCreatedAt());
return create0(alertTrigger);
} catch (TechnicalException ex) {
final String message = "An error occurs while trying to create an alert " + newAlertTrigger;
LOGGER.error(message, ex);
throw new TechnicalManagementException(message, ex);
}
}
private AlertTriggerEntity create0(AlertTrigger alertTrigger) throws TechnicalException {
final AlertTrigger createdAlert = alertTriggerRepository.create(alertTrigger);
final AlertTriggerEntity alertTriggerEntity = convert(createdAlert);
enhance(alertTriggerEntity, alertTriggerEntity.getReferenceType(), alertTriggerEntity.getReferenceId());
// Obviously, we are not deploying rule templates :)
if (!alertTriggerEntity.isTemplate()) {
triggerOrCancelAlert(alertTriggerEntity);
}
return alertTriggerEntity;
}
@Override
public AlertTriggerEntity update(final UpdateAlertTriggerEntity updateAlertTrigger) {
checkAlert();
try {
final Optional alertOptional = alertTriggerRepository.findById(updateAlertTrigger.getId());
if (alertOptional.isPresent()) {
final AlertTrigger alertToUpdate = alertOptional.get();
if (!alertToUpdate.getReferenceId().equals(updateAlertTrigger.getReferenceId())) {
throw new AlertNotFoundException(updateAlertTrigger.getId());
}
AlertTrigger trigger = convert(updateAlertTrigger);
trigger.setId(updateAlertTrigger.getId());
trigger.setReferenceId(alertOptional.get().getReferenceId());
trigger.setReferenceType(alertOptional.get().getReferenceType());
trigger.setCreatedAt(alertOptional.get().getCreatedAt());
trigger.setType(alertOptional.get().getType());
trigger.setUpdatedAt(new Date());
final AlertTriggerEntity alertTriggerEntity = convert(alertTriggerRepository.update(trigger));
enhance(alertTriggerEntity, updateAlertTrigger.getReferenceType(), updateAlertTrigger.getReferenceId());
// Obviously, we are not deploying rule templates :)
if (!alertTriggerEntity.isTemplate()) {
triggerOrCancelAlert(alertTriggerEntity);
}
return alertTriggerEntity;
} else {
throw new AlertNotFoundException(updateAlertTrigger.getId());
}
} catch (TechnicalException ex) {
final String message = "An error occurs while trying to update an alert " + updateAlertTrigger;
LOGGER.error(message, ex);
throw new TechnicalManagementException(message, ex);
}
}
private List findAll() {
try {
final Set triggers = alertTriggerRepository.findAll();
return triggers.stream().map(this::convert).collect(toList());
} catch (TechnicalException ex) {
final String message = "An error occurs while trying to list all alerts";
LOGGER.error(message, ex);
throw new TechnicalManagementException(message, ex);
}
}
@Override
public List findByReference(final AlertReferenceType referenceType, final String referenceId) {
try {
final List triggers = alertTriggerRepository.findByReference(referenceType.name(), referenceId);
return triggers
.stream()
.map(
new Function() {
@Override
public AlertTriggerEntity apply(AlertTrigger alertTrigger) {
AlertTriggerEntity entity = convert(alertTrigger);
getLastEvent(entity.getId())
.ifPresent(
alertEvent -> {
entity.setLastAlertAt(alertEvent.getCreatedAt());
entity.setLastAlertMessage(alertEvent.getMessage());
}
);
final Date from = new Date(System.currentTimeMillis());
Map counters = new HashMap<>();
counters.put(
"5m",
countEvents(entity.getId(), from.toInstant().minus(Duration.ofMinutes(5)).toEpochMilli(), from.getTime())
);
counters.put(
"1h",
countEvents(entity.getId(), from.toInstant().minus(Duration.ofHours(1)).toEpochMilli(), from.getTime())
);
counters.put(
"1d",
countEvents(entity.getId(), from.toInstant().minus(Duration.ofDays(1)).toEpochMilli(), from.getTime())
);
counters.put(
"1M",
countEvents(entity.getId(), from.toInstant().minus(Duration.ofDays(30)).toEpochMilli(), from.getTime())
);
entity.setCounters(counters);
return entity;
}
}
)
.sorted(comparing(AlertTriggerEntity::getName))
.collect(toList());
} catch (TechnicalException ex) {
final String message = "An error occurs while trying to list alerts by reference " + referenceType + '/' + referenceId;
LOGGER.error(message, ex);
throw new TechnicalManagementException(message, ex);
}
}
@Override
public void delete(final String alertId, final String referenceId) {
try {
final Optional optionalAlert = alertTriggerRepository.findById(alertId);
if (!optionalAlert.isPresent() || !optionalAlert.get().getReferenceId().equals(referenceId)) {
throw new AlertNotFoundException(alertId);
}
final AlertTriggerEntity alert = convert(optionalAlert.get());
// Remove from repository
alertTriggerRepository.delete(alertId);
alertEventRepository.deleteAll(alertId);
// Notify alert plugins
disableTrigger(alert);
} catch (TechnicalException te) {
final String msg = "An error occurs while trying to delete the alert " + alertId;
LOGGER.error(msg, te);
throw new TechnicalManagementException(msg, te);
}
}
@Override
public Page findEvents(final String alertId, final AlertEventQuery eventQuery) {
Page alertEventsRepo = alertEventRepository.search(
new AlertEventCriteria.Builder().alert(alertId).from(eventQuery.getFrom()).to(eventQuery.getTo()).build(),
new PageableBuilder().pageNumber(eventQuery.getPageNumber()).pageSize(eventQuery.getPageSize()).build()
);
if (alertEventsRepo.getPageElements() == 0) {
return new Page<>(Collections.emptyList(), 1, 0, 0);
}
List alertEvents = alertEventsRepo
.getContent()
.stream()
.map(
new Function() {
@Override
public AlertEventEntity apply(AlertEvent alertEventRepo) {
AlertEventEntity alertEvent = new AlertEventEntity();
alertEvent.setCreatedAt(alertEventRepo.getCreatedAt());
alertEvent.setMessage(alertEventRepo.getMessage());
return alertEvent;
}
}
)
.collect(toList());
return new Page<>(
alertEvents,
alertEventsRepo.getPageNumber(),
(int) alertEventsRepo.getPageElements(),
alertEventsRepo.getTotalElements()
);
}
private Set findByEvent(AlertEventType event) {
try {
LOGGER.debug("findByEvent: {}", event);
Set set = alertTriggerRepository
.findAll()
.stream()
.filter(
alert ->
alert.isTemplate() &&
alert.getEventRules() != null &&
alert.getEventRules().stream().map(AlertEventRule::getEvent).collect(Collectors.toList()).contains(event)
)
.map(this::convert)
.sorted(Comparator.comparing(AlertTriggerEntity::getName))
.collect(Collectors.toCollection(LinkedHashSet::new));
LOGGER.debug("findByEvent : {} - DONE", set);
return set;
} catch (TechnicalException ex) {
LOGGER.error("An error occurs while trying to find alert triggers by event", ex);
throw new TechnicalManagementException("An error occurs while trying to find alert triggers by event", ex);
}
}
@Override
public void createDefaults(AlertReferenceType referenceType, String referenceId) {
if (getStatus().isEnabled()) {
Set defaultAlerts = findByEvent(AlertEventType.API_CREATE);
for (AlertTriggerEntity alert : defaultAlerts) {
AlertTrigger trigger = convert(alert);
AlertTriggerEntity triggerEntity = convert(trigger);
triggerEntity.setId(UUID.toString(UUID.random()));
triggerEntity.setReferenceType(AlertReferenceType.API);
triggerEntity.setReferenceId(referenceId);
triggerEntity.setTemplate(false);
triggerEntity.setEnabled(true);
triggerEntity.setEventRules(null);
triggerEntity.setParentId(alert.getId());
triggerEntity.setCreatedAt(new Date());
triggerEntity.setUpdatedAt(trigger.getCreatedAt());
try {
create0(convert(triggerEntity));
} catch (TechnicalException te) {
LOGGER.error("Unable to create default alert", te);
}
}
}
}
@Override
public void applyDefaults(final String alertId, final AlertReferenceType referenceType) {
try {
final Optional optionalAlert = alertTriggerRepository.findById(alertId);
if (!optionalAlert.isPresent()) {
throw new AlertNotFoundException(alertId);
}
final AlertTriggerEntity alert = convert(optionalAlert.get());
if (!alert.isTemplate()) {
throw new AlertTemplateInvalidException(alertId);
}
if (referenceType == AlertReferenceType.API) {
apiRepository
.search(null)
.stream()
.map(Api::getId)
.forEach(
new Consumer() {
@Override
public void accept(String apiId) {
try {
boolean create = alertTriggerRepository
.findByReference(AlertReferenceType.API.name(), apiId)
.stream()
.noneMatch(alertTrigger -> alertId.equals(alertTrigger.getParentId()));
if (create) {
AlertTrigger trigger = convert(alert);
AlertTriggerEntity triggerEntity = convert(trigger);
triggerEntity.setId(UUID.toString(UUID.random()));
triggerEntity.setReferenceType(AlertReferenceType.API);
triggerEntity.setReferenceId(apiId);
triggerEntity.setTemplate(false);
triggerEntity.setEnabled(true);
triggerEntity.setEventRules(null);
triggerEntity.setParentId(alertId);
triggerEntity.setCreatedAt(new Date());
triggerEntity.setUpdatedAt(trigger.getCreatedAt());
create0(convert(triggerEntity));
}
} catch (TechnicalException te) {
LOGGER.error("Unable to create default alert for API {}", apiId, te);
}
}
}
);
}
} catch (TechnicalException te) {
final String msg = "An error occurs while trying to apply template alert " + alertId;
LOGGER.error(msg, te);
throw new TechnicalManagementException(msg, te);
}
}
private void enhance(final AlertTriggerEntity trigger, final AlertReferenceType referenceType, final String referenceId) {
// Notifications
List notifications = trigger.getNotifications();
if (notifications == null) {
notifications = new ArrayList<>();
trigger.setNotifications(notifications);
}
// Set the email notifier configuration in case
notifications.forEach(
new Consumer() {
@Override
public void accept(Notification notification) {
if (NotifierServiceImpl.DEFAULT_EMAIL_NOTIFIER_ID.equalsIgnoreCase(notification.getType())) {
setDefaultEmailNotifier(notification);
}
}
}
);
// Filters
List filters = trigger.getFilters();
if (filters == null) {
filters = new ArrayList<>();
trigger.setFilters(filters);
}
switch (referenceType) {
case API:
case APPLICATION:
filters.add(StringCondition.equals(referenceType.name().toLowerCase(), referenceId).build());
break;
}
}
private void setDefaultEmailNotifier(Notification notification) {
EmailNotifierConfiguration configuration = new EmailNotifierConfiguration();
if (host == null) {
configuration.setHost(parameterService.find(Key.EMAIL_HOST, ParameterReferenceType.ORGANIZATION));
final String emailPort = parameterService.find(Key.EMAIL_PORT, ParameterReferenceType.ORGANIZATION);
if (emailPort != null) {
configuration.setPort(Integer.parseInt(emailPort));
}
configuration.setUsername(parameterService.find(Key.EMAIL_USERNAME, ParameterReferenceType.ORGANIZATION));
configuration.setPassword(parameterService.find(Key.EMAIL_PASSWORD, ParameterReferenceType.ORGANIZATION));
configuration.setStartTLSEnabled(parameterService.findAsBoolean(Key.EMAIL_HOST, ParameterReferenceType.ORGANIZATION));
} else {
configuration.setHost(host);
configuration.setPort(Integer.parseInt(port));
configuration.setUsername(username);
configuration.setPassword(password);
configuration.setStartTLSEnabled(startTLSEnabled);
configuration.setSslKeyStore(sslKeyStore);
configuration.setSslKeyStorePassword(sslKeyStorePassword);
configuration.setSslTrustAll(sslTrustAll);
}
try {
JsonNode emailNode = mapper.readTree(notification.getConfiguration());
configuration.setFrom(emailNode.path("from").asText());
configuration.setTo(emailNode.path("to").asText());
configuration.setSubject(String.format(subject, emailNode.path("subject").asText()));
configuration.setBody(emailNode.path("body").asText());
notification.setConfiguration(mapper.writeValueAsString(configuration));
notification.setType("email-notifier");
} catch (IOException e) {
LOGGER.error("Unexpected error while converting system email configuration to email notifier");
}
}
private void checkAlert() {
if (
!parameterService.findAsBoolean(Key.ALERT_ENABLED, ParameterReferenceType.ORGANIZATION) ||
triggerProviderManager.findAll().isEmpty()
) {
throw new AlertUnavailableException();
}
}
private void triggerOrCancelAlert(final Trigger trigger) {
if (trigger.isEnabled()) {
pushTrigger(trigger);
} else {
disableTrigger(trigger);
}
}
private void pushTrigger(final Trigger trigger) {
triggerProvider.register(trigger);
}
private void disableTrigger(Trigger trigger) {
trigger.setEnabled(false);
triggerProvider.unregister(trigger);
}
@Override
public void afterPropertiesSet() throws Exception {
triggerProvider.addListener(
new TriggerProvider.OnConnectionListener() {
@Override
public void doOnConnect() {
LOGGER.info("Connected to alerting system. Sync alert triggers...");
// On reconnect, ensure to push all the triggers again
findAll()
.stream()
.filter(alertTriggerEntity -> !alertTriggerEntity.isTemplate())
.forEach(
new Consumer() {
@Override
public void accept(AlertTriggerEntity alertTriggerEntity) {
enhance(alertTriggerEntity, alertTriggerEntity.getReferenceType(), alertTriggerEntity.getReferenceId());
triggerOrCancelAlert(alertTriggerEntity);
}
}
);
LOGGER.info("Alert triggers synchronized with the alerting system.");
}
}
);
triggerProvider.addListener(
new TriggerProvider.OnDisconnectionListener() {
@Override
public void doOnDisconnect() {
LOGGER.error("Connection with the alerting system has been loose.");
}
}
);
triggerProvider.addListener(
new TriggerProvider.OnCommandListener() {
@Override
public void doOnCommand(Command command) {
if (command instanceof AlertNotificationCommand) {
handleAlertNotificationCommand((AlertNotificationCommand) command);
} else {
LOGGER.warn("Unknown alert command: {}", command);
}
}
}
);
triggerProvider.addListener(
new TriggerProvider.OnCommandResultListener() {
@Override
public void doOnCommand(Command command, Handler resultHandler) {
Supplier supplier = null;
if (command instanceof ResolvePropertyCommand) {
supplier = (Supplier) new ResolvePropertyCommandHandler((ResolvePropertyCommand) command);
} else {
LOGGER.warn("Unknown alert command: {}", command);
}
if (supplier != null) {
resultHandler.handle(supplier.get());
} else {
resultHandler.handle(null);
}
}
}
);
}
private void handleAlertNotificationCommand(AlertNotificationCommand command) {
try {
AlertEvent alertEvent = new AlertEvent();
alertEvent.setId(UUID.toString(UUID.random()));
alertEvent.setAlert(command.getTrigger());
alertEvent.setCreatedAt(new Date(command.getTimestamp()));
alertEvent.setUpdatedAt(alertEvent.getCreatedAt());
alertEvent.setMessage(command.getMessage());
alertEventRepository.create(alertEvent);
} catch (TechnicalException ex) {
final String message = "An error occurs while trying to create an alert event from command {}" + command;
LOGGER.error(message, ex);
throw new TechnicalManagementException(message, ex);
}
}
private final class ResolvePropertyCommandHandler implements Supplier
© 2015 - 2024 Weber Informatics LLC | Privacy Policy