From 99393dbddf1ae34e9fb32d22224faf57f2b2f4a4 Mon Sep 17 00:00:00 2001
From: Michael Breu <michael.breu@uibk.ac.at>
Date: Mon, 27 May 2024 11:03:37 +0000
Subject: [PATCH] =?UTF-8?q?Resolve=20"Sharing=20Plattform=20st=C3=BCrzt=20?=
 =?UTF-8?q?jeden=20Tag=20um=2010=20Uhr=20ab,"?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../jpa/PersistenceAuditEventRepository.java  | 13 +++--
 .../gitsearch/service/AuditEventService.java  | 47 ++++++++++++++----
 .../service/AuditEventServiceIT.java          | 49 +++++++++++++++----
 3 files changed, 88 insertions(+), 21 deletions(-)

diff --git a/src/main/java/at/ac/uibk/gitsearch/repository/jpa/PersistenceAuditEventRepository.java b/src/main/java/at/ac/uibk/gitsearch/repository/jpa/PersistenceAuditEventRepository.java
index 1c05967ba..58b58527e 100644
--- a/src/main/java/at/ac/uibk/gitsearch/repository/jpa/PersistenceAuditEventRepository.java
+++ b/src/main/java/at/ac/uibk/gitsearch/repository/jpa/PersistenceAuditEventRepository.java
@@ -3,10 +3,11 @@ package at.ac.uibk.gitsearch.repository.jpa;
 import at.ac.uibk.gitsearch.domain.PersistentAuditEvent;
 import java.time.Instant;
 import java.util.List;
-import java.util.stream.Stream;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
 import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
 
 /**
  * Spring Data JPA repository for the {@link PersistentAuditEvent} entity.
@@ -18,7 +19,13 @@ public interface PersistenceAuditEventRepository extends JpaRepository<Persisten
 
     Page<PersistentAuditEvent> findAllByAuditEventDateBetween(Instant fromDate, Instant toDate, Pageable pageable);
 
-    List<PersistentAuditEvent> findByAuditEventDateBefore(Instant before);
+    List<PersistentAuditEvent> findTop1000ByAuditEventDateBefore(Instant before);
 
-    Stream<PersistentAuditEvent> streamByAuditEventDateBefore(Instant before);
+    /**
+     * @Deprecated due to https://hibernate.atlassian.net/browse/HHH-5528
+     */
+    @Deprecated
+    @Modifying
+    @Query("delete from PersistentAuditEvent ae where ae.auditEventDate < ?1")
+    int deleteInBulkByRoleId(Instant before);
 }
diff --git a/src/main/java/at/ac/uibk/gitsearch/service/AuditEventService.java b/src/main/java/at/ac/uibk/gitsearch/service/AuditEventService.java
index 46a7a696e..50491cb67 100644
--- a/src/main/java/at/ac/uibk/gitsearch/service/AuditEventService.java
+++ b/src/main/java/at/ac/uibk/gitsearch/service/AuditEventService.java
@@ -1,9 +1,11 @@
 package at.ac.uibk.gitsearch.service;
 
+import at.ac.uibk.gitsearch.domain.PersistentAuditEvent;
 import at.ac.uibk.gitsearch.repository.jpa.AuditEventConverter;
 import at.ac.uibk.gitsearch.repository.jpa.PersistenceAuditEventRepository;
 import java.time.Instant;
 import java.time.temporal.ChronoUnit;
+import java.util.List;
 import java.util.Optional;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -12,7 +14,12 @@ import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionStatus;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.transaction.support.TransactionCallback;
+import org.springframework.transaction.support.TransactionTemplate;
 import tech.jhipster.config.JHipsterProperties;
 
 /**
@@ -24,22 +31,29 @@ import tech.jhipster.config.JHipsterProperties;
 @Transactional
 public class AuditEventService {
 
-    private final Logger log = LoggerFactory.getLogger(AuditEventService.class);
+    @SuppressWarnings("unused")
+    private static final Logger LOGGER = LoggerFactory.getLogger(AuditEventService.class);
 
     private final JHipsterProperties jHipsterProperties;
 
     private final PersistenceAuditEventRepository persistenceAuditEventRepository;
 
+    private final TransactionTemplate isolatedTransactionTemplate;
+
     private final AuditEventConverter auditEventConverter;
 
     public AuditEventService(
         PersistenceAuditEventRepository persistenceAuditEventRepository,
         AuditEventConverter auditEventConverter,
-        JHipsterProperties jhipsterProperties
+        JHipsterProperties jhipsterProperties,
+        PlatformTransactionManager platformTransactionManager
     ) {
         this.persistenceAuditEventRepository = persistenceAuditEventRepository;
         this.auditEventConverter = auditEventConverter;
         this.jHipsterProperties = jhipsterProperties;
+
+        isolatedTransactionTemplate = new TransactionTemplate(platformTransactionManager);
+        isolatedTransactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
     }
 
     /**
@@ -49,13 +63,28 @@ public class AuditEventService {
      */
     @Scheduled(cron = "0 0 23 * * ?")
     public void removeOldAuditEvents() {
-        persistenceAuditEventRepository
-            .streamByAuditEventDateBefore(Instant.now().minus(jHipsterProperties.getAuditEvents().getRetentionPeriod(), ChronoUnit.DAYS))
-            .forEach(auditEvent -> {
-                log.debug("Deleting audit data {}", auditEvent);
-                persistenceAuditEventRepository.delete(auditEvent);
-                persistenceAuditEventRepository.flush();
-            });
+        boolean next = true;
+
+        while (next) {
+            next =
+                isolatedTransactionTemplate.execute(
+                    new TransactionCallback<Boolean>() {
+                        @Override
+                        public Boolean doInTransaction(TransactionStatus status) {
+                            final List<PersistentAuditEvent> auditEventDeleteCandidates = persistenceAuditEventRepository.findTop1000ByAuditEventDateBefore(
+                                Instant.now().minus(jHipsterProperties.getAuditEvents().getRetentionPeriod(), ChronoUnit.DAYS)
+                            );
+                            persistenceAuditEventRepository.deleteAll(auditEventDeleteCandidates);
+                            return !auditEventDeleteCandidates.isEmpty();
+                        }
+                    }
+                );
+        }
+        // not working, due to https://hibernate.atlassian.net/browse/HHH-5528
+        //        int count = persistenceAuditEventRepository.deleteInBulkByRoleId(
+        //            Instant.now().minus(jHipsterProperties.getAuditEvents().getRetentionPeriod(), ChronoUnit.DAYS)
+        //        );
+        //        LOGGER.info("cleaned up {} outdated AuditEnvets", count);
     }
 
     @Transactional(readOnly = true)
diff --git a/src/test/java/at/ac/uibk/gitsearch/service/AuditEventServiceIT.java b/src/test/java/at/ac/uibk/gitsearch/service/AuditEventServiceIT.java
index 0b0d49432..b8f418552 100644
--- a/src/test/java/at/ac/uibk/gitsearch/service/AuditEventServiceIT.java
+++ b/src/test/java/at/ac/uibk/gitsearch/service/AuditEventServiceIT.java
@@ -13,9 +13,17 @@ import java.util.concurrent.TimeUnit;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Timeout;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionStatus;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.transaction.support.TransactionCallback;
+import org.springframework.transaction.support.TransactionTemplate;
 import tech.jhipster.config.JHipsterProperties;
 
 /**
@@ -25,6 +33,11 @@ import tech.jhipster.config.JHipsterProperties;
 @Transactional
 public class AuditEventServiceIT {
 
+    private final Logger log = LoggerFactory.getLogger(AuditEventServiceIT.class);
+
+    @Autowired
+    private PlatformTransactionManager platformTransactionManager;
+
     @Autowired
     private AuditEventService auditEventService;
 
@@ -40,13 +53,16 @@ public class AuditEventServiceIT {
 
     private PersistentAuditEvent auditEventNew;
 
+    private static final int MAX_OUTDATEDVALUES = 4500;
+
     @BeforeEach
     public void init() {
+        log.info("Preparing {} outdated AuditEvents", MAX_OUTDATEDVALUES);
         auditEventsOld = new ArrayList<>();
-        for (int i = 1; i <= 1000; i++) {
+        for (int i = 1; i <= MAX_OUTDATEDVALUES; i++) {
             PersistentAuditEvent auditEventOld = new PersistentAuditEvent();
             auditEventOld.setAuditEventDate(
-                Instant.now().minus(jHipsterProperties.getAuditEvents().getRetentionPeriod() + i, ChronoUnit.DAYS)
+                Instant.now().minus((jHipsterProperties.getAuditEvents().getRetentionPeriod() + 1) * 24 + i, ChronoUnit.HOURS)
             );
             auditEventOld.setPrincipal("test-user-old");
             auditEventOld.setAuditEventType("test-type");
@@ -54,6 +70,7 @@ public class AuditEventServiceIT {
             auditEventOld.getData().put("someDate", "someValue" + i);
             auditEventsOld.add(auditEventOld);
         }
+        log.info("Prepared {} outdated AuditEvents", MAX_OUTDATEDVALUES);
         auditEventWithinRetention = new PersistentAuditEvent();
         auditEventWithinRetention.setAuditEventDate(
             Instant.now().minus(jHipsterProperties.getAuditEvents().getRetentionPeriod() - 1, ChronoUnit.DAYS)
@@ -69,16 +86,30 @@ public class AuditEventServiceIT {
 
     @Test
     @Timeout(value = 5, unit = TimeUnit.MINUTES)
-    @Transactional
+    @DirtiesContext
     public void verifyOldAuditEventsAreDeleted() {
-        persistenceAuditEventRepository.deleteAll();
-        auditEventsOld.forEach(auditEventOld -> persistenceAuditEventRepository.save(auditEventOld));
-        persistenceAuditEventRepository.save(auditEventWithinRetention);
-        persistenceAuditEventRepository.save(auditEventNew);
+        TransactionTemplate isolatedTransactionTemplate = new TransactionTemplate(platformTransactionManager);
+        isolatedTransactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
+
+        isolatedTransactionTemplate.execute(
+            new TransactionCallback<Boolean>() {
+                @Override
+                public Boolean doInTransaction(TransactionStatus status) {
+                    persistenceAuditEventRepository.deleteAll();
+
+                    auditEventsOld.forEach(auditEventOld -> persistenceAuditEventRepository.save(auditEventOld));
+                    persistenceAuditEventRepository.save(auditEventWithinRetention);
+                    persistenceAuditEventRepository.save(auditEventNew);
+                    return true;
+                }
+            }
+        );
+
+        log.info("Saved {} outdated AuditEvents", MAX_OUTDATEDVALUES);
+        assertThat(persistenceAuditEventRepository.findAll().size()).isGreaterThan(MAX_OUTDATEDVALUES);
 
-        persistenceAuditEventRepository.flush();
         auditEventService.removeOldAuditEvents();
-        persistenceAuditEventRepository.flush();
+        //        persistenceAuditEventRepository.flush();
 
         assertThat(persistenceAuditEventRepository.findAll().size()).isEqualTo(2);
         assertThat(persistenceAuditEventRepository.findByPrincipal("test-user-old")).isEmpty();
-- 
GitLab