Skip to content

Commit fa73280

Browse files
Ben-Edwards-cgiDavidTMannkaren-hedges
authored
DMP-5161: Automated Job - External outbound deletion 404 errors (#2985)
Co-authored-by: David Mann <[email protected]> Co-authored-by: karen-hedges <[email protected]>
1 parent 0c64552 commit fa73280

File tree

14 files changed

+357
-167
lines changed

14 files changed

+357
-167
lines changed
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package uk.gov.hmcts.darts.audio.deleter.impl;
2+
3+
import org.junit.jupiter.api.BeforeEach;
4+
import org.junit.jupiter.api.Test;
5+
import org.springframework.beans.factory.annotation.Autowired;
6+
import uk.gov.hmcts.darts.audio.entity.MediaRequestEntity;
7+
import uk.gov.hmcts.darts.audiorequests.model.AudioRequestType;
8+
import uk.gov.hmcts.darts.common.entity.ObjectRecordStatusEntity;
9+
import uk.gov.hmcts.darts.common.entity.TransformedMediaEntity;
10+
import uk.gov.hmcts.darts.common.entity.TransientObjectDirectoryEntity;
11+
import uk.gov.hmcts.darts.common.entity.UserAccountEntity;
12+
import uk.gov.hmcts.darts.common.util.EodHelper;
13+
import uk.gov.hmcts.darts.testutils.IntegrationBase;
14+
15+
import java.time.Duration;
16+
import java.time.OffsetDateTime;
17+
import java.util.UUID;
18+
19+
import static org.assertj.core.api.Assertions.assertThat;
20+
21+
class ExternalOutboundDataStoreDeleterWithBufferIntTest extends IntegrationBase {
22+
23+
private final String blobId = UUID.randomUUID().toString();
24+
private MediaRequestEntity downloadMediaRequestEntity;
25+
private TransientObjectDirectoryEntity transientObjectDirectoryEntity;
26+
private Integer transformedMediaId;
27+
28+
@Autowired
29+
private ExternalOutboundDataStoreDeleterWithBuffer deleter;
30+
31+
@BeforeEach
32+
void setUp() {
33+
34+
UserAccountEntity requestor = dartsDatabase.getUserAccountStub().getIntegrationTestUserAccountEntity();
35+
downloadMediaRequestEntity = dartsDatabase.createAndLoadOpenMediaRequestEntity(requestor, AudioRequestType.DOWNLOAD);
36+
ObjectRecordStatusEntity datastoreDeletionStatus = EodHelper.datastoreDeletionStatus();
37+
transientObjectDirectoryEntity = dartsDatabase.getTransientObjectDirectoryRepository()
38+
.saveAndFlush(dartsDatabase.getTransientObjectDirectoryStub().createTransientObjectDirectoryEntity(
39+
downloadMediaRequestEntity,
40+
datastoreDeletionStatus,
41+
blobId
42+
));
43+
44+
transformedMediaId = transientObjectDirectoryEntity.getTransformedMedia().getId();
45+
46+
}
47+
48+
@Test
49+
void deleteExpiredTransientObjectEntities_shouldDeleteData_whereTransformedMediaExpired() {
50+
TransformedMediaEntity transformedMediaEntity =
51+
dartsDatabase.getTransformedMediaRepository().findById(transformedMediaId).orElseThrow();
52+
transformedMediaEntity.setExpiryTime(OffsetDateTime.now().minus(Duration.ofDays(90)));
53+
dartsDatabase.save(transformedMediaEntity);
54+
assertThat(dartsDatabase.getTransientObjectDirectoryRepository().getReferenceById(transientObjectDirectoryEntity.getId()))
55+
.isNotNull();
56+
assertThat(dartsDatabase.getTransformedMediaRepository().findByMediaRequestId(downloadMediaRequestEntity.getId())).isNotEmpty();
57+
assertThat(dartsDatabase.getTransformedMediaRepository().getReferenceById(transformedMediaId)).isNotNull();
58+
int batchSize = 10;
59+
60+
deleter.deleteExpiredTransientObjectEntities(batchSize);
61+
62+
// Confirm that the TransientObjectDirectoryEntity and TransformedMediaEntity is deleted
63+
assertThat(dartsDatabase.getTransientObjectDirectoryRepository().findById(transientObjectDirectoryEntity.getId())).isEmpty();
64+
assertThat(dartsDatabase.getTransformedMediaRepository().findByMediaRequestId(downloadMediaRequestEntity.getId())).isEmpty();
65+
assertThat(dartsDatabase.getTransformedMediaRepository().findById(transformedMediaId)).isEmpty();
66+
}
67+
68+
@Test
69+
void deleteExpiredTransientObjectEntities_shouldNotDeleteData_whereTransformedMediaNotExpired() {
70+
TransformedMediaEntity transformedMediaEntity =
71+
dartsDatabase.getTransformedMediaRepository().findById(transformedMediaId).orElseThrow();
72+
transformedMediaEntity.setExpiryTime(OffsetDateTime.now().minus(Duration.ofDays(1)));
73+
dartsDatabase.save(transformedMediaEntity);
74+
assertThat(dartsDatabase.getTransientObjectDirectoryRepository().getReferenceById(transientObjectDirectoryEntity.getId()))
75+
.isNotNull();
76+
assertThat(dartsDatabase.getTransformedMediaRepository().findByMediaRequestId(downloadMediaRequestEntity.getId())).isNotEmpty();
77+
assertThat(dartsDatabase.getTransformedMediaRepository().getReferenceById(transformedMediaId)).isNotNull();
78+
int batchSize = 10;
79+
80+
deleter.deleteExpiredTransientObjectEntities(batchSize);
81+
82+
// Confirm that the TransientObjectDirectoryEntity and TransformedMediaEntity is not deleted
83+
assertThat(dartsDatabase.getTransientObjectDirectoryRepository().findById(transientObjectDirectoryEntity.getId())).isNotNull();
84+
assertThat(dartsDatabase.getTransformedMediaRepository().findByMediaRequestId(downloadMediaRequestEntity.getId())).isNotEmpty();
85+
assertThat(dartsDatabase.getTransformedMediaRepository().findById(transformedMediaId)).isNotNull();
86+
}
87+
88+
}

src/main/java/uk/gov/hmcts/darts/audio/deleter/AbstractExternalDataStoreDeleter.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ public Collection<T> delete(Integer batchSize) {
2929
@Override
3030
public boolean delete(T entityToBeDeleted) {
3131
boolean deletedFromDataStore = deleteFromDataStore(entityToBeDeleted);
32-
deleteFromRepository(entityToBeDeleted);
32+
if (deletedFromDataStore) {
33+
datastoreDeletionCallback(entityToBeDeleted);
34+
}
3335
return deletedFromDataStore;
3436
}
3537

@@ -59,7 +61,7 @@ protected boolean deleteFromDataStore(T entityToBeDeleted) {
5961

6062
protected abstract Collection<T> findItemsToDelete(int batchSize);
6163

62-
protected void deleteFromRepository(T entityToBeDeleted) {
64+
protected void datastoreDeletionCallback(T entityToBeDeleted) {
6365
repository.delete(entityToBeDeleted);
6466
}
6567
}

src/main/java/uk/gov/hmcts/darts/audio/deleter/impl/ExternalOutboundDataStoreDeleter.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ protected Collection<TransientObjectDirectoryEntity> findItemsToDelete(int batch
4040
}
4141

4242
@Override
43-
protected void deleteFromRepository(TransientObjectDirectoryEntity transientObjectDirectoryEntity) {
44-
super.deleteFromRepository(transientObjectDirectoryEntity);
43+
protected void datastoreDeletionCallback(TransientObjectDirectoryEntity transientObjectDirectoryEntity) {
44+
super.datastoreDeletionCallback(transientObjectDirectoryEntity);
4545
if (transientObjectDirectoryEntity.getTransformedMedia() != null) {
4646
log.debug("Deleting transformed media {} with transient object directory id={}",
4747
transientObjectDirectoryEntity.getTransformedMedia().getId(), transientObjectDirectoryEntity.getId());

src/main/java/uk/gov/hmcts/darts/audio/deleter/impl/ExternalOutboundDataStoreDeleterWithBuffer.java

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,22 @@
22

33
import lombok.extern.slf4j.Slf4j;
44
import org.springframework.beans.factory.annotation.Value;
5+
import org.springframework.data.domain.Limit;
56
import org.springframework.stereotype.Service;
67
import uk.gov.hmcts.darts.common.entity.TransformedMediaEntity;
78
import uk.gov.hmcts.darts.common.entity.TransientObjectDirectoryEntity;
9+
import uk.gov.hmcts.darts.common.enums.ObjectRecordStatusEnum;
810
import uk.gov.hmcts.darts.common.helper.CurrentTimeHelper;
911
import uk.gov.hmcts.darts.common.repository.TransformedMediaRepository;
1012
import uk.gov.hmcts.darts.common.repository.TransientObjectDirectoryRepository;
13+
import uk.gov.hmcts.darts.common.util.EodHelper;
1114
import uk.gov.hmcts.darts.datamanagement.api.DataManagementApi;
1215

1316
import java.time.Duration;
1417
import java.time.OffsetDateTime;
18+
import java.util.Collection;
19+
import java.util.List;
20+
import java.util.Objects;
1521

1622
@Service
1723
@Slf4j
@@ -31,12 +37,47 @@ public ExternalOutboundDataStoreDeleterWithBuffer(TransientObjectDirectoryReposi
3137
this.transientObjectDirectoryDeleteBuffer = transientObjectDirectoryDeleteBuffer;
3238
}
3339

40+
3441
@Override
35-
protected void deleteFromRepository(TransientObjectDirectoryEntity transientObjectDirectoryEntity) {
36-
TransformedMediaEntity transformedMedia = transientObjectDirectoryEntity.getTransformedMedia();
42+
public Collection<TransientObjectDirectoryEntity> delete(Integer batchSize) {
43+
Collection<TransientObjectDirectoryEntity> result = super.delete(batchSize);
44+
deleteExpiredTransientObjectEntities(batchSize);
45+
return result;
46+
}
47+
48+
void deleteExpiredTransientObjectEntities(Integer batchSize) {
3749
OffsetDateTime maxTimeToDelete = currentTimeHelper.currentOffsetDateTime().minus(transientObjectDirectoryDeleteBuffer);
38-
if (transformedMedia == null || maxTimeToDelete.isAfter(transformedMedia.getExpiryTime())) {
39-
super.deleteFromRepository(transientObjectDirectoryEntity);
50+
51+
Collection<TransientObjectDirectoryEntity> expiredTransientObjectDirectoryEntities =
52+
this.getRepository().findByTransformedMediaIsNullOrExpiryBeforeMaxExpiryTime(maxTimeToDelete,
53+
ObjectRecordStatusEnum.DATASTORE_DELETED.getId(),
54+
Limit.of(batchSize));
55+
56+
if (expiredTransientObjectDirectoryEntities.isEmpty()) {
57+
log.info("No expired transient object directories found older than {}. Nothing to delete.", maxTimeToDelete);
58+
return;
4059
}
60+
61+
if (log.isInfoEnabled()) {
62+
log.info("Deleting {} expired transient object directories older than {}. Ids: {}",
63+
expiredTransientObjectDirectoryEntities.size(), maxTimeToDelete,
64+
expiredTransientObjectDirectoryEntities.stream()
65+
.map(TransientObjectDirectoryEntity::getId)
66+
.toList());
67+
}
68+
// Delete transformed media entities associated with the expired transient object directories
69+
List<TransformedMediaEntity> transformedMediaEntityList = expiredTransientObjectDirectoryEntities.stream()
70+
.map(TransientObjectDirectoryEntity::getTransformedMedia)
71+
.filter(Objects::nonNull)
72+
.toList();
73+
74+
this.getRepository().deleteAll(expiredTransientObjectDirectoryEntities);
75+
transformedMediaRepository.deleteAll(transformedMediaEntityList);
76+
}
77+
78+
@Override
79+
public void datastoreDeletionCallback(TransientObjectDirectoryEntity transientObjectDirectoryEntity) {
80+
transientObjectDirectoryEntity.setStatus(EodHelper.datastoreDeletionStatus());
81+
this.getRepository().save(transientObjectDirectoryEntity);
4182
}
4283
}

src/main/java/uk/gov/hmcts/darts/audio/service/impl/OutboundAudioDeleterProcessorImpl.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import uk.gov.hmcts.darts.audio.service.OutboundAudioDeleterProcessorSingleElement;
1212
import uk.gov.hmcts.darts.common.entity.TransformedMediaEntity;
1313
import uk.gov.hmcts.darts.common.entity.TransientObjectDirectoryEntity;
14+
import uk.gov.hmcts.darts.common.enums.ObjectRecordStatusEnum;
1415
import uk.gov.hmcts.darts.common.repository.TransformedMediaRepository;
1516
import uk.gov.hmcts.darts.common.repository.UserAccountRepository;
1617

@@ -44,7 +45,10 @@ public List<TransientObjectDirectoryEntity> markForDeletion(Integer batchSize) {
4445

4546
OffsetDateTime deletionStartDateTime = deletionDayCalculator.getStartDateForDeletion(getDeletionDays());
4647

47-
List<Integer> transformedMediaListIds = transformedMediaRepository.findAllDeletableTransformedMedia(deletionStartDateTime, Limit.of(batchSize));
48+
List<Integer> transformedMediaListIds = transformedMediaRepository.findAllDeletableTransformedMedia(
49+
deletionStartDateTime,
50+
ObjectRecordStatusEnum.getExpiredStatusIds(),
51+
Limit.of(batchSize));
4852

4953
if (transformedMediaListIds.isEmpty()) {
5054
log.debug("No transformed media to be marked for deletion");

src/main/java/uk/gov/hmcts/darts/common/enums/ObjectRecordStatusEnum.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import lombok.Getter;
44
import lombok.RequiredArgsConstructor;
55

6+
import java.util.List;
67
import java.util.Map;
78
import java.util.concurrent.ConcurrentHashMap;
89

@@ -34,7 +35,8 @@ public enum ObjectRecordStatusEnum {
3435
ARM_RPO_PENDING(21),
3536
ARM_REPLAY(22),
3637
ARM_MISSING_RESPONSE(23),
37-
ARM_RAW_DATA_PUSHED(24);
38+
ARM_RAW_DATA_PUSHED(24),
39+
DATASTORE_DELETED(25);
3840

3941
private static final Map<Integer, ObjectRecordStatusEnum> BY_ID = new ConcurrentHashMap<>();
4042

@@ -50,4 +52,8 @@ public static ObjectRecordStatusEnum valueOfId(Integer id) {
5052
return BY_ID.get(id);
5153
}
5254

55+
public static List<Integer> getExpiredStatusIds() {
56+
return List.of(MARKED_FOR_DELETION.getId(),
57+
DATASTORE_DELETED.getId());
58+
}
5359
}

src/main/java/uk/gov/hmcts/darts/common/repository/TransformedMediaRepository.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,13 @@ public interface TransformedMediaRepository extends JpaRepository<TransformedMed
5959
(tm.lastAccessed < :createdAtOrLastAccessedDateTime AND tm.mediaRequest.status = 'COMPLETED')
6060
OR (tm.createdDateTime < :createdAtOrLastAccessedDateTime AND tm.mediaRequest.status <> 'PROCESSING' AND tm.lastAccessed IS NULL)
6161
)
62-
AND upper(tod.status.description) <> 'MARKED FOR DELETION'
62+
AND tod.status.id not in :expiredObjectRecordStatusIds
6363
AND not exists (SELECT sge from tm.mediaRequest.currentOwner.securityGroupEntities sge
6464
where sge.securityRoleEntity.roleName = 'MEDIA_IN_PERPETUITY')
6565
""")
66-
List<Integer> findAllDeletableTransformedMedia(OffsetDateTime createdAtOrLastAccessedDateTime, Limit limit);
66+
List<Integer> findAllDeletableTransformedMedia(OffsetDateTime createdAtOrLastAccessedDateTime,
67+
List<Integer> expiredObjectRecordStatusIds,
68+
Limit limit);
6769

6870
@Query("""
6971
SELECT tm

src/main/java/uk/gov/hmcts/darts/common/repository/TransientObjectDirectoryRepository.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import uk.gov.hmcts.darts.common.entity.ObjectRecordStatusEntity;
88
import uk.gov.hmcts.darts.common.entity.TransientObjectDirectoryEntity;
99

10+
import java.time.OffsetDateTime;
1011
import java.util.List;
1112

1213
@Repository
@@ -19,11 +20,21 @@ public interface TransientObjectDirectoryRepository extends JpaRepository<Transi
1920
""")
2021
List<TransientObjectDirectoryEntity> findByTransformedMediaId(Integer transformedMediaId);
2122

23+
//Join required to ensure transient media is loaded into session
2224
@Query("""
2325
SELECT tod FROM TransientObjectDirectoryEntity tod
2426
LEFT JOIN FETCH tod.transformedMedia
2527
WHERE tod.status = :status
2628
""")
27-
//Join required to ensure transient media is loaded into session
2829
List<TransientObjectDirectoryEntity> findByStatus(ObjectRecordStatusEntity status, Limit limit);
30+
31+
32+
@Query("""
33+
select tod from TransientObjectDirectoryEntity tod
34+
LEFT JOIN tod.transformedMedia tm
35+
where tm is null or tm.expiryTime < :maxExpiryTime
36+
and tod.status.id = :statusId
37+
""")
38+
List<TransientObjectDirectoryEntity> findByTransformedMediaIsNullOrExpiryBeforeMaxExpiryTime(
39+
OffsetDateTime maxExpiryTime, Integer statusId, Limit limit);
2940
}

src/main/java/uk/gov/hmcts/darts/common/util/EodHelper.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import static uk.gov.hmcts.darts.common.enums.ObjectRecordStatusEnum.ARM_RESPONSE_PROCESSING_FAILED;
3232
import static uk.gov.hmcts.darts.common.enums.ObjectRecordStatusEnum.ARM_RPO_PENDING;
3333
import static uk.gov.hmcts.darts.common.enums.ObjectRecordStatusEnum.AWAITING_VERIFICATION;
34+
import static uk.gov.hmcts.darts.common.enums.ObjectRecordStatusEnum.DATASTORE_DELETED;
3435
import static uk.gov.hmcts.darts.common.enums.ObjectRecordStatusEnum.FAILURE;
3536
import static uk.gov.hmcts.darts.common.enums.ObjectRecordStatusEnum.MARKED_FOR_DELETION;
3637
import static uk.gov.hmcts.darts.common.enums.ObjectRecordStatusEnum.STORED;
@@ -82,6 +83,8 @@ public class EodHelper {
8283
private static ObjectRecordStatusEntity awaitingVerificationStatus;
8384
@Getter
8485
private static ObjectRecordStatusEntity armRawDataPushedStatus;
86+
@Getter
87+
private static ObjectRecordStatusEntity datastoreDeletionStatus;
8588

8689
@Getter
8790
private static List<ObjectRecordStatusEntity> failedArmStatuses;
@@ -115,8 +118,8 @@ public void init() {
115118
armReplayStatus = orsRepository.findById(ARM_REPLAY.getId()).orElseThrow();
116119
armMissingResponseStatus = orsRepository.findById(ARM_MISSING_RESPONSE.getId()).orElseThrow();
117120
armRawDataPushedStatus = orsRepository.findById(ARM_RAW_DATA_PUSHED.getId()).orElseThrow();
121+
datastoreDeletionStatus = orsRepository.findById(DATASTORE_DELETED.getId()).orElseThrow();
118122
failedArmStatuses = List.of(failedArmRawDataStatus, failedArmManifestFileStatus, armResponseManifestFailedStatus);
119-
120123
}
121124

122125
public static boolean isEqual(ExternalLocationTypeEntity olt1, ExternalLocationTypeEntity olt2) {
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
--Add new object record status for deleted objects. Manully set the ors_id to ensure it aligns with the enum value
2+
INSERT INTO object_record_status (ors_id, ors_description)
3+
VALUES (25, 'Deleted from datastore');
4+
--Update the sequence to ensure it continues from the last inserted value
5+
alter sequence ors_seq restart with 26;

0 commit comments

Comments
 (0)