Skip to content

Commit f8a1ee9

Browse files
Merge pull request #1009 from digitalservicebund/delete-after-publish-finished
delete-after-publish-finished
2 parents 55322fb + 791f2e9 commit f8a1ee9

File tree

11 files changed

+184
-150
lines changed

11 files changed

+184
-150
lines changed

backend/src/main/java/de/bund/digitalservice/ris/norms/adapter/output/s3/BucketService.java

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import de.bund.digitalservice.ris.norms.utils.XmlMapper;
99
import java.io.InputStream;
1010
import java.nio.charset.StandardCharsets;
11+
import java.time.Instant;
1112
import java.util.ArrayList;
1213
import java.util.List;
1314
import lombok.extern.slf4j.Slf4j;
@@ -51,8 +52,8 @@ public class BucketService
5152
PublishPrivateNormPort,
5253
DeletePublicNormPort,
5354
DeletePrivateNormPort,
54-
DeleteAllPublicNormsPort,
55-
DeleteAllPrivateNormsPort,
55+
DeleteAllPublicDokumentePort,
56+
DeleteAllPrivateDokumentePort,
5657
PublishChangelogsPort {
5758

5859
@Value("${otc.obs.private.bucket-name}")
@@ -103,13 +104,23 @@ public void deletePublicNorm(DeletePublicNormPort.Command command) throws Bucket
103104
}
104105

105106
@Override
106-
public void deleteAllPublicNorms() {
107-
deleteAllExceptChangelog(publicS3Client, publicBucketName);
107+
public void deleteAllPublicDokumente(DeleteAllPublicDokumentePort.Command command) {
108+
deleteAllDokumenteLastModifiedBefore(
109+
publicS3Client,
110+
publicBucketName,
111+
publicChangelog,
112+
command.lastChangeBefore()
113+
);
108114
}
109115

110116
@Override
111-
public void deleteAllPrivateNorms() {
112-
deleteAllExceptChangelog(privateS3Client, privateBucketName);
117+
public void deleteAllPrivateDokumente(DeleteAllPrivateDokumentePort.Command command) {
118+
deleteAllDokumenteLastModifiedBefore(
119+
privateS3Client,
120+
privateBucketName,
121+
privateChangelog,
122+
command.lastChangeBefore()
123+
);
113124
}
114125

115126
@Override
@@ -235,7 +246,8 @@ private void deleteFromBucket(
235246
}
236247

237248
/**
238-
* Deletes all objects in the specified S3 bucket, except for the changelog files, which are contained within the "changelogs" folder
249+
* Deletes all Dokumente in the specified S3 bucket, (not the changelog files) which have not been changed since the
250+
* given date.
239251
* The deletion process handles pagination automatically if there are more than 1,000 objects in the bucket.
240252
* <p>
241253
* AWS S3 allows a maximum of 1,000 keys to be processed per delete request. This method retrieves and deletes objects
@@ -244,10 +256,20 @@ private void deleteFromBucket(
244256
*
245257
* @param s3Client the S3 client used to interact with the S3 service
246258
* @param bucketName the name of the S3 bucket where the objects are located
259+
* @param lastChangeBefore Dokumente that have been changed since this date are ignored
247260
*/
248-
private void deleteAllExceptChangelog(final S3Client s3Client, final String bucketName) {
261+
private void deleteAllDokumenteLastModifiedBefore(
262+
final S3Client s3Client,
263+
final String bucketName,
264+
Changelog changelog,
265+
Instant lastChangeBefore
266+
) {
249267
try {
250-
ListObjectsV2Request listRequest = ListObjectsV2Request.builder().bucket(bucketName).build();
268+
ListObjectsV2Request listRequest = ListObjectsV2Request
269+
.builder()
270+
.bucket(bucketName)
271+
.prefix("eli")
272+
.build();
251273
ListObjectsV2Response listResponse;
252274
int objectsDeleted = 0;
253275
do {
@@ -256,7 +278,10 @@ private void deleteAllExceptChangelog(final S3Client s3Client, final String buck
256278

257279
for (S3Object s3Object : listResponse.contents()) {
258280
final String key = s3Object.key();
259-
if (!key.startsWith(Changelog.FOLDER + "/")) {
281+
if (
282+
!key.startsWith(Changelog.FOLDER + "/") &&
283+
s3Object.lastModified().isBefore(lastChangeBefore)
284+
) {
260285
objectsToDelete.add(ObjectIdentifier.builder().key(key).build());
261286
}
262287
}
@@ -268,6 +293,9 @@ private void deleteAllExceptChangelog(final S3Client s3Client, final String buck
268293
.build();
269294
s3Client.deleteObjects(deleteRequest);
270295
objectsDeleted += objectsToDelete.size();
296+
objectsToDelete.forEach(objectIdentifier ->
297+
changelog.addContent(Changelog.DELETED, objectIdentifier.key())
298+
);
271299
}
272300

273301
listRequest =
@@ -278,7 +306,7 @@ private void deleteAllExceptChangelog(final S3Client s3Client, final String buck
278306
throw new BucketException(
279307
BucketException.Operation.DELETE,
280308
bucketName,
281-
"All norms could not be deleted",
309+
"All dokumente could not be deleted",
282310
e
283311
);
284312
}

backend/src/main/java/de/bund/digitalservice/ris/norms/adapter/output/s3/Changelog.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import com.fasterxml.jackson.databind.ObjectMapper;
66
import java.io.IOException;
77
import java.nio.file.Paths;
8-
import java.time.LocalDate;
8+
import java.time.Instant;
99
import java.util.*;
1010
import lombok.Getter;
1111

@@ -15,7 +15,7 @@
1515
public class Changelog {
1616

1717
public static final String FOLDER = "changelogs";
18-
public static final String FILE_NAME_FORMAT = "changelog-%s.json";
18+
public static final String FILE_NAME_FORMAT = "%s-norms.json";
1919

2020
public static final String CHANGED = "changed";
2121
public static final String DELETED = "deleted";
@@ -27,7 +27,8 @@ public class Changelog {
2727
private final String fileName;
2828

2929
public Changelog() {
30-
this.fileName = Paths.get(FOLDER, FILE_NAME_FORMAT.formatted(LocalDate.now())).toString();
30+
this.fileName =
31+
Paths.get(FOLDER, FILE_NAME_FORMAT.formatted(Instant.now().toString())).toString();
3132
}
3233

3334
/**

backend/src/main/java/de/bund/digitalservice/ris/norms/adapter/output/s3/S3MockClient.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import jakarta.annotation.PostConstruct;
44
import java.io.*;
55
import java.nio.file.*;
6+
import java.time.Instant;
67
import java.util.List;
78
import java.util.Objects;
89
import java.util.stream.Stream;
@@ -73,7 +74,11 @@ public ListObjectsV2Response listObjectsV2(ListObjectsV2Request listObjectsV2Req
7374
// If the file is not under 'eli', return only the file name
7475
key = path.getFileName().toString();
7576
}
76-
return S3Object.builder().key(key).build();
77+
return S3Object
78+
.builder()
79+
.key(key)
80+
.lastModified(Instant.ofEpochMilli(path.toFile().lastModified()))
81+
.build();
7782
})
7883
.toList();
7984

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package de.bund.digitalservice.ris.norms.application.port.output;
2+
3+
import de.bund.digitalservice.ris.norms.domain.entity.Dokument;
4+
import java.time.Instant;
5+
6+
/**
7+
* Interface representing the output port for deleting multiple {@link Dokument} entities from a storage location designated
8+
* for private data.
9+
*/
10+
public interface DeleteAllPrivateDokumentePort {
11+
/**
12+
* Deletes all {@link Dokument} entities that have not been edited since the given date from a designated private
13+
* storage location.
14+
*
15+
* @param command command for deleting Dokumente
16+
*/
17+
void deleteAllPrivateDokumente(DeleteAllPrivateDokumentePort.Command command);
18+
19+
/**
20+
* Command for deleting dokumente
21+
*
22+
* @param lastChangeBefore Dokumente last edited after the given date are not deleted.
23+
*/
24+
record Command(Instant lastChangeBefore) {}
25+
}

backend/src/main/java/de/bund/digitalservice/ris/norms/application/port/output/DeleteAllPrivateNormsPort.java

Lines changed: 0 additions & 15 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package de.bund.digitalservice.ris.norms.application.port.output;
2+
3+
import de.bund.digitalservice.ris.norms.domain.entity.Dokument;
4+
import java.time.Instant;
5+
6+
/**
7+
* Interface representing the output port for deleting all {@link Dokument} entities from a storage location designated
8+
* for public data.
9+
*/
10+
public interface DeleteAllPublicDokumentePort {
11+
/**
12+
* Deletes all {@link Dokument} entities that have not been edited since the given date from a designated public
13+
* storage location.
14+
*
15+
* @param command command for deleting Dokumente
16+
*/
17+
void deleteAllPublicDokumente(DeleteAllPublicDokumentePort.Command command);
18+
19+
/**
20+
* Command for deleting dokumente
21+
*
22+
* @param lastChangeBefore Dokumente last edited after the given date are not deleted.
23+
*/
24+
record Command(Instant lastChangeBefore) {}
25+
}

backend/src/main/java/de/bund/digitalservice/ris/norms/application/port/output/DeleteAllPublicNormsPort.java

Lines changed: 0 additions & 14 deletions
This file was deleted.

backend/src/main/java/de/bund/digitalservice/ris/norms/application/service/PublishService.java

Lines changed: 39 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import de.bund.digitalservice.ris.norms.domain.entity.eli.NormManifestationEli;
88
import de.bund.digitalservice.ris.norms.utils.NodeParser;
99
import de.bund.digitalservice.ris.norms.utils.exceptions.StorageException;
10+
import java.time.Instant;
1011
import java.time.LocalDate;
1112
import java.time.ZoneId;
1213
import java.time.ZoneOffset;
@@ -33,8 +34,8 @@ public class PublishService implements PublishNormUseCase {
3334
private final DeletePublicNormPort deletePublicNormPort;
3435
private final DeletePrivateNormPort deletePrivateNormPort;
3536
private final LoadLastMigrationLogPort loadLastMigrationLogPort;
36-
private final DeleteAllPublicNormsPort deleteAllPublicNormsPort;
37-
private final DeleteAllPrivateNormsPort deleteAllPrivateNormsPort;
37+
private final DeleteAllPublicDokumentePort deleteAllPublicDokumentePort;
38+
private final DeleteAllPrivateDokumentePort deleteAllPrivateDokumentePort;
3839
private final PublishChangelogsPort publishChangelogsPort;
3940

4041
public PublishService(
@@ -46,8 +47,8 @@ public PublishService(
4647
DeletePublicNormPort deletePublicNormPort,
4748
DeletePrivateNormPort deletePrivateNormPort,
4849
LoadLastMigrationLogPort loadLastMigrationLogPort,
49-
DeleteAllPublicNormsPort deleteAllPublicNormsPort,
50-
DeleteAllPrivateNormsPort deleteAllPrivateNormsPort,
50+
DeleteAllPublicDokumentePort deleteAllPublicDokumentePort,
51+
DeleteAllPrivateDokumentePort deleteAllPrivateDokumentePort,
5152
PublishChangelogsPort publishChangelogsPort
5253
) {
5354
this.loadNormManifestationElisByPublishStatePort = loadNormManifestationElisByPublishStatePort;
@@ -58,55 +59,64 @@ public PublishService(
5859
this.deletePublicNormPort = deletePublicNormPort;
5960
this.deletePrivateNormPort = deletePrivateNormPort;
6061
this.loadLastMigrationLogPort = loadLastMigrationLogPort;
61-
this.deleteAllPublicNormsPort = deleteAllPublicNormsPort;
62-
this.deleteAllPrivateNormsPort = deleteAllPrivateNormsPort;
62+
this.deleteAllPublicDokumentePort = deleteAllPublicDokumentePort;
63+
this.deleteAllPrivateDokumentePort = deleteAllPrivateDokumentePort;
6364
this.publishChangelogsPort = publishChangelogsPort;
6465
}
6566

6667
@Override
6768
public void processQueuedFilesForPublish() {
69+
final Instant startOfProcessing = Instant.now();
70+
final LocalDate today = startOfProcessing.atZone(ZoneId.systemDefault()).toLocalDate();
71+
72+
List<NormManifestationEli> manifestationElis =
73+
loadNormManifestationElisByPublishStatePort.loadNormManifestationElisByPublishState(
74+
new LoadNormManifestationElisByPublishStatePort.Command(NormPublishState.QUEUED_FOR_PUBLISH)
75+
);
76+
77+
log.info("Found {} norms that are queued for publishing", manifestationElis.size());
78+
79+
manifestationElis.forEach(manifestationEli -> {
80+
log.info("Processing norm with manifestation eli {}", manifestationEli);
81+
Optional<Norm> norm = loadNormPort.loadNorm(new LoadNormPort.Command(manifestationEli));
82+
norm.ifPresent(this::processNorm);
83+
if (norm.isEmpty()) {
84+
log.error("Norm with manifestation eli {} not found", manifestationEli);
85+
}
86+
});
87+
6888
loadLastMigrationLogPort
6989
.loadLastMigrationLog()
7090
.ifPresent(migrationLog -> {
7191
final LocalDate createdAtDate = migrationLog
7292
.getCreatedAt()
7393
.atZone(ZoneId.systemDefault())
7494
.toLocalDate();
75-
final LocalDate today = LocalDate.now();
7695
final LocalDate yesterday = today.minusDays(1);
7796
if (createdAtDate.equals(today) || createdAtDate.equals(yesterday)) {
7897
log.info(
79-
"Migration log found with timestamp {} (UTC). Deleting all norms in both buckets",
98+
"Migration log found with timestamp {} (UTC) and {} dokumente.",
8099
migrationLog
81100
.getCreatedAt()
82101
.atOffset(ZoneOffset.UTC)
83-
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
102+
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
103+
migrationLog.getSize()
84104
);
85105
if (migrationLog.getSize() <= 0) {
86106
throw new MigrationJobException();
87107
}
88-
deleteAllPublicNormsPort.deleteAllPublicNorms();
89-
deleteAllPrivateNormsPort.deleteAllPrivateNorms();
90-
log.info("Deleted all norms in both buckets");
108+
log.info("Deleting all old dokumente in both buckets");
109+
deleteAllPublicDokumentePort.deleteAllPublicDokumente(
110+
new DeleteAllPublicDokumentePort.Command(startOfProcessing)
111+
);
112+
deleteAllPrivateDokumentePort.deleteAllPrivateDokumente(
113+
new DeleteAllPrivateDokumentePort.Command(startOfProcessing)
114+
);
115+
log.info("Deleted all dokumente in both buckets");
91116
}
92117
});
93118

94-
List<NormManifestationEli> manifestationElis =
95-
loadNormManifestationElisByPublishStatePort.loadNormManifestationElisByPublishState(
96-
new LoadNormManifestationElisByPublishStatePort.Command(NormPublishState.QUEUED_FOR_PUBLISH)
97-
);
98-
99-
log.info("Found {} norms that are queued for publishing", manifestationElis.size());
100-
101-
manifestationElis.forEach(manifestationEli -> {
102-
log.info("Processing norm with manifestation eli {}", manifestationEli);
103-
Optional<Norm> norm = loadNormPort.loadNorm(new LoadNormPort.Command(manifestationEli));
104-
norm.ifPresent(this::processNorm);
105-
if (norm.isEmpty()) {
106-
log.error("Norm with manifestation eli {} not found", manifestationEli);
107-
}
108-
});
109-
publishChangelogsPort.publishChangelogs(new PublishChangelogsPort.Command(true));
119+
publishChangelogsPort.publishChangelogs(new PublishChangelogsPort.Command(false));
110120
log.info("Publish job successfully completed.");
111121
}
112122

@@ -167,7 +177,7 @@ private void rollbackPrivatePublish(Norm norm) {
167177
try {
168178
deletePrivateNormPort.deletePrivateNorm(new DeletePrivateNormPort.Command(norm));
169179
log.info(
170-
"Deleted privated norm on rollback strategy: {}",
180+
"Deleted private norm on rollback strategy: {}",
171181
norm.getManifestationEli().toString()
172182
);
173183
} catch (StorageException e) {

backend/src/test/java/de/bund/digitalservice/ris/norms/adapter/output/s3/ChangelogTest.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
import com.fasterxml.jackson.core.JsonProcessingException;
66
import java.io.IOException;
7-
import java.time.LocalDate;
87
import org.junit.jupiter.api.Test;
98

109
class ChangelogTest {
@@ -14,7 +13,7 @@ void createsChangelogWithGivenDate() {
1413
final Changelog changelog = new Changelog();
1514
assertThat(changelog.getFileName()).isNotEmpty();
1615
assertThat(changelog.getFileName())
17-
.isEqualTo("changelogs/changelog-%s.json".formatted(LocalDate.now().toString()));
16+
.matches("changelogs/\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d+Z-norms\\.json");
1817
}
1918

2019
@Test

0 commit comments

Comments
 (0)