diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c05352a0fc318df99098d9bc409c1e2cc54de00d..064aef969d8ddff3882ababafd2ade497e9df7ef 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -110,7 +110,6 @@ maven-test: - export APPLICATION_GITLAB_ADMINACCESSTOKEN=$APPLICATION_GITLAB_ADMINACCESSTOKEN - export APPLICATION_GITLAB_GENERALACCESSTOKEN=$APPLICATION_GITLAB_GENERALACCESSTOKEN - export MAIL_FROM=no-reply@uibk.ac.at - frontend-test: stage: test cache: diff --git a/pom.xml b/pom.xml index 592ec78290e0ded48f12c9d4fc4b9f127906ea5a..f46bbb52ac9916060500d4fc6b670a084eb07a42 100644 --- a/pom.xml +++ b/pom.xml @@ -91,6 +91,8 @@ <lifecycle-mapping.version>1.0.0</lifecycle-mapping.version> <properties-maven-plugin.version>1.0.0</properties-maven-plugin.version> <sonar-maven-plugin.version>3.9.1.2184</sonar-maven-plugin.version> + + <skipUTs>${skipTests}</skipUTs><!-- skip plain unit tests --> <!-- jhipster-needle-maven-property --> </properties> @@ -859,6 +861,7 @@ <exclude>**/*IT*</exclude> <exclude>**/*IntTest*</exclude> </excludes> + <skipTests>${skipUTs}</skipTests> </configuration> </plugin> <plugin> diff --git a/src/main/java/at/ac/uibk/gitsearch/repository/jpa/CustomAuditEventRepository.java b/src/main/java/at/ac/uibk/gitsearch/repository/jpa/CustomAuditEventRepository.java index bb2c46fdec5aba5da60f149f9d83bf4de3b6e559..2e682e4104454a0a962ead79ae95edf6d3271ed7 100644 --- a/src/main/java/at/ac/uibk/gitsearch/repository/jpa/CustomAuditEventRepository.java +++ b/src/main/java/at/ac/uibk/gitsearch/repository/jpa/CustomAuditEventRepository.java @@ -62,7 +62,7 @@ public class CustomAuditEventRepository implements AuditEventRepository { persistentAuditEvent.setAuditEventType(event.getType()); persistentAuditEvent.setAuditEventDate(event.getTimestamp()); Map<String, String> eventData = auditEventConverter.convertDataToStrings(event.getData()); - persistentAuditEvent.setData(truncate(eventData)); + persistentAuditEvent.getData().putAll(truncate(eventData)); persistenceAuditEventRepository.save(persistentAuditEvent); } } 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 5b1f77ac74c2fec098f287d709aa10303567b40e..29d0b2b413d9c0884f2d922655cc6513e665b75a 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 @@ -6,8 +6,6 @@ import java.util.List; 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. @@ -22,10 +20,12 @@ public interface PersistenceAuditEventRepository extends JpaRepository<Persisten List<PersistentAuditEvent> findTop1000ByAuditEventDateBefore(Instant before); /** - * @deprecated due to https://hibernate.atlassian.net/browse/HHH-5528 + * + * Unsafe due to https://hibernate.atlassian.net/browse/HHH-5529. + * It needs to load all AuditEvents before deletion, in order to remove + * EnitityCollection(!) + * May be solved when migrating to hibernate > 6.0.0.Beta2 */ - @Deprecated(since = "June 2024") - @Modifying - @Query("delete from PersistentAuditEvent ae where ae.auditEventDate < ?1") - int deleteInBulkByRoleId(Instant before); + + int deleteByAuditEventDateBefore(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 42c16a74235afa3250848035b560cfb3863fbd7e..223a621c64458953da4dd80b01d1356c7586346a 100644 --- a/src/main/java/at/ac/uibk/gitsearch/service/AuditEventService.java +++ b/src/main/java/at/ac/uibk/gitsearch/service/AuditEventService.java @@ -17,6 +17,7 @@ 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.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionTemplate; import tech.jhipster.config.JHipsterProperties; @@ -30,17 +31,16 @@ import tech.jhipster.config.JHipsterProperties; @Transactional public class AuditEventService { - @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; + private final PlatformTransactionManager platformTransactionManager; + public AuditEventService( PersistenceAuditEventRepository persistenceAuditEventRepository, AuditEventConverter auditEventConverter, @@ -50,9 +50,7 @@ public class AuditEventService { this.persistenceAuditEventRepository = persistenceAuditEventRepository; this.auditEventConverter = auditEventConverter; this.jHipsterProperties = jhipsterProperties; - - isolatedTransactionTemplate = new TransactionTemplate(platformTransactionManager); - isolatedTransactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + this.platformTransactionManager = platformTransactionManager; } /** @@ -61,7 +59,13 @@ public class AuditEventService { * This is scheduled to get fired at 11:00 (pm) UTC. */ @Scheduled(cron = "0 0 23 * * ?") + @Transactional(propagation = Propagation.REQUIRES_NEW) public int removeOldAuditEvents() { + final TransactionTemplate isolatedTransactionTemplate = new TransactionTemplate(platformTransactionManager); + isolatedTransactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + isolatedTransactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED); // this is mainly + // to enable + // testing boolean next = true; AtomicInteger count = new AtomicInteger(0); @@ -76,9 +80,21 @@ public class AuditEventService { return !auditEventDeleteCandidates.isEmpty(); }); } + LOGGER.info("Cleaned Up {} outdated audit events", count.intValue()); return count.intValue(); } + /** + * unsafe, because hibernate loads all events before deletion! + * + * @return + */ + @Transactional(propagation = Propagation.REQUIRES_NEW) + public int removeOutdatedAuditEvents2() { + Instant deleteBefore = Instant.now().minus(jHipsterProperties.getAuditEvents().getRetentionPeriod(), ChronoUnit.DAYS); + return this.persistenceAuditEventRepository.deleteByAuditEventDateBefore(deleteBefore); + } + @Transactional(readOnly = true) public Page<AuditEvent> findAll(Pageable pageable) { return persistenceAuditEventRepository.findAll(pageable).map(auditEventConverter::convertToAuditEvent); diff --git a/src/test/java/at/ac/uibk/gitsearch/repository/CustomAuditEventRepositoryIT.java b/src/test/java/at/ac/uibk/gitsearch/repository/CustomAuditEventRepositoryIT.java index 891d0a38f6215adae5668e3974fe0ae1b236a0b4..d2e6ab68723b789d793e90c4f6dec9b75c5d06cf 100644 --- a/src/test/java/at/ac/uibk/gitsearch/repository/CustomAuditEventRepositoryIT.java +++ b/src/test/java/at/ac/uibk/gitsearch/repository/CustomAuditEventRepositoryIT.java @@ -52,7 +52,8 @@ class CustomAuditEventRepositoryIT { testUserEvent.setAuditEventDate(oneHourAgo); Map<String, String> data = new HashMap<>(); data.put("test-key", "test-value"); - testUserEvent.setData(data); + testUserEvent.getData().clear(); + testUserEvent.getData().putAll(data); PersistentAuditEvent testOldUserEvent = new PersistentAuditEvent(); testOldUserEvent.setPrincipal("test-user"); 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 4951d5c15cf4b90b30f246a1b4e28312f214b371..b13dc5f1b3c9f17822a0f2e951df09b68ea94023 100644 --- a/src/test/java/at/ac/uibk/gitsearch/service/AuditEventServiceIT.java +++ b/src/test/java/at/ac/uibk/gitsearch/service/AuditEventServiceIT.java @@ -10,9 +10,7 @@ import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; -import org.junit.Assert; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.slf4j.Logger; @@ -22,9 +20,7 @@ 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; @@ -60,32 +56,26 @@ public class AuditEventServiceIT { private int currentAuditLogs; @BeforeEach - @Timeout(value = 5, unit = TimeUnit.MINUTES) - public void init() { - currentAuditLogs = persistenceAuditEventRepository.findAll().size(); - log.info("There where already {} AuditEvents", currentAuditLogs); - final List<PersistentAuditEvent> auditEventDeleteCandidates = persistenceAuditEventRepository.findTop1000ByAuditEventDateBefore( - Instant.now().minus(jHipsterProperties.getAuditEvents().getRetentionPeriod(), ChronoUnit.DAYS) - ); - Assert.assertTrue("There are already outdated Elements?", auditEventDeleteCandidates.size() == 0); - + @Timeout(value = 5, unit = TimeUnit.MINUTES) // just for manuall debugging + public void writevariousLogs() { log.info("Preparing {} outdated AuditEvents", MAX_OUTDATEDVALUES); auditEventsOld = new ArrayList<>(); for (int i = 1; i <= MAX_OUTDATEDVALUES; i++) { PersistentAuditEvent auditEventOld = new PersistentAuditEvent(); auditEventOld.setAuditEventDate( - Instant.now().minus((jHipsterProperties.getAuditEvents().getRetentionPeriod() + 1) * 24 + i, ChronoUnit.DAYS) + Instant.now().minus((jHipsterProperties.getAuditEvents().getRetentionPeriod() + 2) + i, ChronoUnit.DAYS) ); auditEventOld.setPrincipal("test-user-old"); auditEventOld.setAuditEventType("test-type"); - auditEventOld.getData().put("someDate", "someValue" + i); + auditEventOld.getData().put("someData", "someValue" + i); + auditEventOld.getData().put("someExtraData", "secondValue" + 2 * 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) + Instant.now().minus(jHipsterProperties.getAuditEvents().getRetentionPeriod() - 2, ChronoUnit.DAYS) ); auditEventWithinRetention.setPrincipal("test-user-retention"); auditEventWithinRetention.setAuditEventType("test-type"); @@ -94,49 +84,59 @@ public class AuditEventServiceIT { auditEventNew.setAuditEventDate(Instant.now()); auditEventNew.setPrincipal("test-user-new"); auditEventNew.setAuditEventType("test-type"); + + TransactionTemplate isolatedTransactionTemplate = new TransactionTemplate(platformTransactionManager); + isolatedTransactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + + isolatedTransactionTemplate.executeWithoutResult(status -> { + persistenceAuditEventRepository.deleteAll(); + currentAuditLogs = persistenceAuditEventRepository.findAll().size(); + persistenceAuditEventRepository.saveAll(auditEventsOld); + persistenceAuditEventRepository.save(auditEventWithinRetention); + persistenceAuditEventRepository.save(auditEventNew); + persistenceAuditEventRepository.flush(); + assertThat(persistenceAuditEventRepository.findAll().size()).isEqualTo(currentAuditLogs + auditEventsOld.size() + 2); + log.info("Saved {} outdated AuditEvents into database", auditEventsOld.size()); + }); } @Test - @Disabled // currently not working @Timeout(value = 5, unit = TimeUnit.MINUTES) @DirtiesContext public void verifyOldAuditEventsAreDeleted() { + assertThat(persistenceAuditEventRepository.findAll().size()).isEqualTo(currentAuditLogs + auditEventsOld.size() + 2); + + log.info("Removing outdated AuditEvents"); + int deleted = auditEventService.removeOldAuditEvents(); + assertThat(deleted).isEqualTo(MAX_OUTDATEDVALUES); + log.info("Removed {} outdated AuditEvents", deleted); + TransactionTemplate isolatedTransactionTemplate = new TransactionTemplate(platformTransactionManager); isolatedTransactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + isolatedTransactionTemplate.executeWithoutResult(status -> { + assertThat(persistenceAuditEventRepository.findAll().size()).isEqualTo(currentAuditLogs + 2); + assertThat(persistenceAuditEventRepository.findByPrincipal("test-user-old")).isEmpty(); + assertThat(persistenceAuditEventRepository.findByPrincipal("test-user-retention")).isNotEmpty(); + assertThat(persistenceAuditEventRepository.findByPrincipal("test-user-new")).isNotEmpty(); + }); + } - 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; - } - } - ); + @Test + @Timeout(value = 5, unit = TimeUnit.MINUTES) + @DirtiesContext + public void verifyOldAuditEventsAreDeleted2() { + assertThat(persistenceAuditEventRepository.findAll().size()).isEqualTo(currentAuditLogs + auditEventsOld.size() + 2); - isolatedTransactionTemplate.execute( - new TransactionCallback<Boolean>() { - @Override - public Boolean doInTransaction(TransactionStatus status) { - log.info("Saved {} outdated AuditEvents", MAX_OUTDATEDVALUES); - assertThat(persistenceAuditEventRepository.findAll().size()).isGreaterThan(MAX_OUTDATEDVALUES); - - log.info("Removing outdated AuditEvents"); - int count = auditEventService.removeOldAuditEvents(); - // persistenceAuditEventRepository.flush(); - - log.info("Removed {} outdated AuditEvents", count); - assertThat(persistenceAuditEventRepository.findAll().size()).isEqualTo(currentAuditLogs + 2); - assertThat(persistenceAuditEventRepository.findByPrincipal("test-user-old")).isEmpty(); - assertThat(persistenceAuditEventRepository.findByPrincipal("test-user-retention")).isNotEmpty(); - assertThat(persistenceAuditEventRepository.findByPrincipal("test-user-new")).isNotEmpty(); - return true; - } - } - ); + int deleted = this.auditEventService.removeOutdatedAuditEvents2(); + assertThat(deleted).isEqualTo(MAX_OUTDATEDVALUES); + + TransactionTemplate isolatedTransactionTemplate = new TransactionTemplate(platformTransactionManager); + isolatedTransactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + isolatedTransactionTemplate.executeWithoutResult(status -> { + assertThat(persistenceAuditEventRepository.findAll().size()).isEqualTo(currentAuditLogs + 2); + assertThat(persistenceAuditEventRepository.findByPrincipal("test-user-old")).isEmpty(); + assertThat(persistenceAuditEventRepository.findByPrincipal("test-user-retention")).isNotEmpty(); + assertThat(persistenceAuditEventRepository.findByPrincipal("test-user-new")).isNotEmpty(); + }); } } diff --git a/src/test/resources/config/application.yml b/src/test/resources/config/application.yml index e6d8ec5f2c2ef1e8cf377d23141f6ef426b62f67..5a59bd84ca7f00cea9429c995c4688d71fda785a 100644 --- a/src/test/resources/config/application.yml +++ b/src/test/resources/config/application.yml @@ -40,8 +40,17 @@ spring: name: username: password: + # url: jdbc:mysql://localhost:3307/gitsearch?useUnicode=true&characterEncoding=utf8&allowPublicKeyRetrieval=true&useSSL=false&useLegacyDatetimeCode=false&serverTimezone=UTC&createDatabaseIfNotExist=true + # username: root + # password: simple hikari: + poolName: Hikari auto-commit: false + # data-source-properties: + # cachePrepStmts: true + # prepStmtCacheSize: 250 + # prepStmtCacheSqlLimit: 2048 + # useServerPrepStmts: true jackson: serialization: write-durations-as-timestamps: false