ApplicationEventHandler.java
/*
* Copyright (C) 2025 B3Partners B.V.
*
* SPDX-License-Identifier: MIT
*/
package org.tailormap.api.repository.events;
import static org.tailormap.api.prometheus.TagNames.METRICS_APP_ID_TAG;
import static org.tailormap.api.prometheus.TagNames.METRICS_APP_REQUEST_COUNTER_NAME;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Set;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.rest.core.annotation.HandleAfterDelete;
import org.springframework.data.rest.core.annotation.HandleBeforeSave;
import org.springframework.data.rest.core.annotation.RepositoryEventHandler;
import org.springframework.stereotype.Component;
import org.tailormap.api.persistence.Application;
import org.tailormap.api.persistence.json.AppTreeLayerNode;
import org.tailormap.api.prometheus.PrometheusService;
@Component
@RepositoryEventHandler
public class ApplicationEventHandler {
private static final Logger logger =
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private final PrometheusService prometheusService;
@Value("${tailormap-api.allowed-metrics}")
private Set<String> allowedMetrics;
public ApplicationEventHandler(PrometheusService prometheusService) {
this.prometheusService = prometheusService;
}
/**
* Handle after delete. Delete any associated task.
*
* @param application the application that was deleted
*/
@HandleAfterDelete
public void afterDeleteApplicationEventHandler(Application application) {
logger.trace("Application '{}' (id: {}) was deleted.", application.getName(), application.getId());
if (prometheusService.isPrometheusAvailable()) {
// cleanup any metrics from Prometheus or other systems if needed
ArrayList<String> metricsToDelete = new ArrayList<>();
// application metrics
metricsToDelete.add(METRICS_APP_REQUEST_COUNTER_NAME + "_total{" + METRICS_APP_ID_TAG + "=\""
+ application.getId().toString() + "\"}");
// application layer metrics
for (String metricName : allowedMetrics) {
metricsToDelete.add(metricName + "_total{" + METRICS_APP_ID_TAG + "=\""
+ application.getId().toString() + "\"}");
}
try {
logger.info(
"Cleaning up all metrics for deleted application with id: {}, (name: '{}')",
application.getId(),
application.getName());
prometheusService.deleteMetric(metricsToDelete.toArray(new String[0]));
} catch (IOException | URISyntaxException e) {
logger.error("Error cleaning up metrics for deleted application with id: {}", application.getId(), e);
}
}
}
@HandleBeforeSave
public void beforeSaveApplicationEventHandler(Application application) {
if (prometheusService.isPrometheusAvailable()) {
// cleanup any metrics from Prometheus if needed
Set<String> oldNodeIds = application
.getAllOldAppTreeLayerNode()
.map(AppTreeLayerNode::getId)
.collect(Collectors.toSet());
Set<String> updatedNodeIds = application
.getAllAppTreeLayerNode()
.map(AppTreeLayerNode::getId)
.collect(Collectors.toSet());
// Find old nodes in application that are not in the updated application (we don't care about new nodes)
Set<String> removedNodeIds = oldNodeIds.stream()
.filter(id -> !updatedNodeIds.contains(id))
.collect(Collectors.toSet());
if (!removedNodeIds.isEmpty()) {
ArrayList<String> metricsToDelete = new ArrayList<>();
for (String metricName : allowedMetrics) {
for (String nodeId : removedNodeIds) {
metricsToDelete.add(metricName + "_total{" + METRICS_APP_ID_TAG + "=\""
+ application.getId().toString() + "\",appLayerId=\"" + nodeId + "\"}");
}
}
try {
logger.info(
"Cleaning up application layer metrics for application with id: {}, (name: '{}'), removed nodes: {}",
application.getId(),
application.getName(),
removedNodeIds);
prometheusService.deleteMetric(metricsToDelete.toArray(new String[0]));
} catch (IOException | URISyntaxException e) {
logger.error(
"Error cleaning up layer metrics for updated application with id: {}",
application.getId(),
e);
}
}
}
}
}