Skip to content

Commit afbb883

Browse files
committed
adopt full potential of spring-cloud-azure-starter-storage-blob
1 parent 96ea705 commit afbb883

File tree

4 files changed

+85
-119
lines changed

4 files changed

+85
-119
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,3 +187,5 @@ helm install mucsi96/spring-app \
187187
- https://flowbite.com/docs/components/tables/
188188
- https://hslpicker.com/
189189
- https://learn.microsoft.com/en-us/azure/developer/java/spring-framework/spring-security-support?tabs=SpringCloudAzure5x#accessing-a-resource-server
190+
191+
- https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/spring/spring-cloud-azure-core/src/main/java/com/azure/spring/cloud/core/resource

server/src/main/java/io/github/mucsi96/postgresbackuptool/service/BackupService.java

Lines changed: 80 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -2,140 +2,138 @@
22

33
import java.io.File;
44
import java.io.IOException;
5+
import java.io.OutputStream;
6+
import java.nio.file.Files;
7+
import java.nio.file.StandardCopyOption;
58
import java.time.Duration;
69
import java.time.Instant;
710
import java.time.format.DateTimeFormatter;
8-
import java.util.Collections;
11+
import java.util.Arrays;
912
import java.util.List;
10-
import java.util.Optional;
1113

1214
import org.springframework.beans.factory.annotation.Value;
15+
import org.springframework.core.io.Resource;
16+
import org.springframework.core.io.ResourceLoader;
17+
import org.springframework.core.io.WritableResource;
1318
import org.springframework.stereotype.Service;
1419

20+
import com.azure.spring.cloud.core.resource.AzureStorageBlobProtocolResolver;
1521
import com.azure.storage.blob.BlobContainerClient;
16-
import com.azure.storage.blob.BlobServiceClient;
17-
import com.azure.storage.blob.models.BlobItem;
18-
import com.azure.storage.blob.models.ListBlobsOptions;
1922

2023
import io.github.mucsi96.postgresbackuptool.model.Backup;
2124
import lombok.extern.slf4j.Slf4j;
2225

2326
@Service
2427
@Slf4j
2528
public class BackupService {
26-
private final BlobServiceClient blobServiceClient;
2729
private final DateTimeFormatter dateTimeFormatter;
2830
private final String containerName;
29-
30-
public BackupService(BlobServiceClient blobServiceClient,
31-
DateTimeFormatter dateTimeFormatter,
32-
@Value("${blobstorage.containerName}") String containerName) {
33-
this.blobServiceClient = blobServiceClient;
31+
private final ResourceLoader resourceLoader;
32+
private final AzureStorageBlobProtocolResolver azureStorageBlobProtocolResolver;
33+
private final BlobContainerClient blobContainerClient;
34+
35+
public BackupService(DateTimeFormatter dateTimeFormatter,
36+
@Value("${spring.cloud.azure.storage.blob.container-name}") String containerName,
37+
ResourceLoader resourceLoader,
38+
AzureStorageBlobProtocolResolver azureStorageBlobProtocolResolver,
39+
BlobContainerClient blobContainerClient) {
3440
this.dateTimeFormatter = dateTimeFormatter;
3541
this.containerName = containerName;
42+
this.resourceLoader = resourceLoader;
43+
this.azureStorageBlobProtocolResolver = azureStorageBlobProtocolResolver;
44+
this.blobContainerClient = blobContainerClient;
3645
}
3746

3847
public List<Backup> getBackups(String prefix) {
39-
return Optional.of(blobServiceClient.getBlobContainerClient(containerName))
40-
.filter(BlobContainerClient::exists)
41-
.map(container -> listAndTransformBlobs(container, prefix))
42-
.orElse(Collections.emptyList());
43-
}
44-
45-
private List<Backup> listAndTransformBlobs(BlobContainerClient container, String prefix) {
46-
return container
47-
.listBlobs(new ListBlobsOptions().setPrefix(prefix + "/"), null)
48-
.stream()
49-
.filter(blob -> blob.getName().endsWith(".zip")) // Only ZIP files
50-
.map(blob -> createBackupFromBlob(blob, prefix))
51-
.sorted((a, b) -> b.getLastModified().compareTo(a.getLastModified()))
52-
.toList();
48+
try {
49+
Resource[] resources = azureStorageBlobProtocolResolver
50+
.getResources(String.format("azure-blob://%s/%s/*.zip",
51+
containerName, prefix));
52+
return Arrays.stream(resources)
53+
.map(resource -> createBackupFromResource(resource))
54+
.sorted((a, b) -> b.getLastModified()
55+
.compareTo(a.getLastModified()))
56+
.toList();
57+
} catch (IOException e) {
58+
log.error("Failed to list backups", e);
59+
return List.of();
60+
}
5361
}
5462

55-
private Backup createBackupFromBlob(BlobItem blob, String prefix) {
56-
String name = getBackupName(prefix, blob);
57-
58-
// Parse metadata from filename
59-
return Backup.builder()
60-
.name(name)
61-
.lastModified(parseBackupTimestamp(name))
62-
.size(blob.getProperties().getContentLength())
63-
.totalRowCount(getTotalCountFromName(prefix, blob))
64-
.fileCount(getFileCountFromName(prefix, blob))
65-
.filesTotalSize(getFilesTotalSizeFromName(prefix, blob))
66-
.retentionPeriod(getRetentionPeriodFromName(prefix, blob))
67-
.hasPlainDump(true)
68-
.build();
63+
private Backup createBackupFromResource(Resource resource) {
64+
String name = resource.getFilename();
65+
try {
66+
return Backup.builder().name(name)
67+
.lastModified(parseBackupTimestamp(name))
68+
.size(resource.contentLength())
69+
.totalRowCount(getTotalCountFromName(name))
70+
.fileCount(getFileCountFromName(name))
71+
.filesTotalSize(getFilesTotalSizeFromName(name))
72+
.retentionPeriod(getRetentionPeriodFromName(name))
73+
.hasPlainDump(true).build();
74+
} catch (IOException e) {
75+
throw new RuntimeException("Failed to get resource content length",
76+
e);
77+
}
6978
}
7079

7180
private Instant parseBackupTimestamp(String name) {
7281
return dateTimeFormatter.parse(name.substring(0, 15), Instant::from);
7382
}
7483

75-
public void createBackup(String prefix, File dumpFile, String fileName) {
76-
BlobContainerClient blobContainerClient = blobServiceClient
77-
.getBlobContainerClient(containerName);
78-
79-
blobContainerClient.getBlobClient(prefix + "/" + fileName)
80-
.uploadFromFile(dumpFile.getAbsolutePath());
84+
public void createBackup(String prefix, File dumpFile, String fileName)
85+
throws IOException {
86+
String location = String.format("azure-blob://%s/%s/%s", containerName,
87+
prefix, fileName);
88+
WritableResource resource = (WritableResource) resourceLoader
89+
.getResource(location);
90+
try (OutputStream os = resource.getOutputStream()) {
91+
Files.copy(dumpFile.toPath(), os);
92+
}
8193
}
8294

8395
public File downloadBackup(String prefix, String key) throws IOException {
84-
BlobContainerClient blobContainerClient = blobServiceClient
85-
.getBlobContainerClient(containerName);
86-
87-
blobContainerClient.getBlobClient(prefix + "/" + key)
88-
.downloadToFile(key);
89-
90-
return new File(key);
96+
String location = String.format("azure-blob://%s/%s/%s", containerName,
97+
prefix, key);
98+
Resource resource = resourceLoader.getResource(location);
99+
File file = new File(key);
100+
Files.copy(resource.getInputStream(), file.toPath(),
101+
StandardCopyOption.REPLACE_EXISTING);
102+
return file;
91103
}
92104

93105
public void cleanup(String prefix) {
94-
BlobContainerClient blobContainerClient = blobServiceClient
95-
.getBlobContainerClient(containerName);
96-
97-
blobContainerClient
98-
.listBlobs(new ListBlobsOptions().setPrefix(prefix + "/"), null)
99-
.stream().filter(blobItem -> shouldCleanup(prefix, blobItem))
100-
.forEach(blobItem -> blobContainerClient
101-
.getBlobClient(blobItem.getName()).delete());
106+
getBackups(prefix).stream().filter(this::shouldCleanup)
107+
.forEach(backup -> blobContainerClient
108+
.getBlobClient(prefix + "/" + backup.getName())
109+
.delete());
102110
}
103111

104-
public Optional<Instant> getLastBackupTime(String prefix) {
112+
public java.util.Optional<Instant> getLastBackupTime(String prefix) {
105113
return getBackups(prefix).stream().findFirst()
106114
.map(Backup::getLastModified);
107115
}
108116

109-
private int getTotalCountFromName(String prefix, BlobItem backup) {
110-
return Integer.parseInt(getBackupName(prefix, backup).split("\\.")[1]);
117+
private int getTotalCountFromName(String name) {
118+
return Integer.parseInt(name.split("\\.")[1]);
111119
}
112120

113-
private int getFileCountFromName(String prefix, BlobItem backup) {
114-
return Integer.parseInt(getBackupName(prefix, backup).split("\\.")[2]);
121+
private int getFileCountFromName(String name) {
122+
return Integer.parseInt(name.split("\\.")[2]);
115123
}
116124

117-
private long getFilesTotalSizeFromName(String prefix, BlobItem backup) {
118-
return Long.parseLong(getBackupName(prefix, backup).split("\\.")[3]);
125+
private long getFilesTotalSizeFromName(String name) {
126+
return Long.parseLong(name.split("\\.")[3]);
119127
}
120128

121-
private int getRetentionPeriodFromName(String prefix, BlobItem backup) {
122-
return Integer.parseInt(getBackupName(prefix, backup).split("\\.")[4]);
129+
private int getRetentionPeriodFromName(String name) {
130+
return Integer.parseInt(name.split("\\.")[4]);
123131
}
124132

125-
private boolean shouldCleanup(String prefix, BlobItem backup) {
126-
Backup b = Backup.builder().name(backup.getName())
127-
.lastModified(dateTimeFormatter.parse(
128-
getBackupName(prefix, backup).substring(0, 15),
129-
Instant::from))
130-
.retentionPeriod(getRetentionPeriodFromName(prefix, backup))
131-
.build();
132-
Instant cleanupDate = b.getLastModified()
133-
.plus(Duration.ofDays(b.getRetentionPeriod()));
133+
private boolean shouldCleanup(Backup backup) {
134+
Instant cleanupDate = backup.getLastModified()
135+
.plus(Duration.ofDays(backup.getRetentionPeriod()));
134136

135137
return cleanupDate.isBefore(Instant.now());
136138
}
137-
138-
private static String getBackupName(String prefix, BlobItem backup) {
139-
return backup.getName().substring(prefix.length() + 1);
140-
}
141139
}

server/src/main/resources/META-INF/additional-spring-configuration-metadata.json

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,41 +4,6 @@
44
"name": "databasesConfigPath",
55
"type": "java.lang.String",
66
"description": "Path to the databases configuration file"
7-
},
8-
{
9-
"name": "blobstorage.clientId",
10-
"type": "java.lang.String",
11-
"description": "Azure Blob Storage client ID for authentication"
12-
},
13-
{
14-
"name": "blobstorage.tenantId",
15-
"type": "java.lang.String",
16-
"description": "Azure Blob Storage tenant ID for authentication"
17-
},
18-
{
19-
"name": "blobstorage.clientSecret",
20-
"type": "java.lang.String",
21-
"description": "Azure Blob Storage client secret for authentication"
22-
},
23-
{
24-
"name": "blobstorage.accountUrl",
25-
"type": "java.lang.String",
26-
"description": "Azure Blob Storage account URL"
27-
},
28-
{
29-
"name": "blobstorage.containerName",
30-
"type": "java.lang.String",
31-
"description": "Azure Blob Storage container name"
32-
},
33-
{
34-
"name": "blobstorage.publicUrl",
35-
"type": "java.lang.String",
36-
"description": "Publicly accessible URL for Azure Blob Storage"
37-
},
38-
{
39-
"name": "blobstorage.connectionString",
40-
"type": "java.lang.String",
41-
"description": "Azure Blob Storage connection string"
427
}
438
]
449
}

server/src/main/resources/application.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ spring:
1515
credential:
1616
client-id: ${api-client-id}
1717

18-
blobstorage:
19-
containerName: ${storage-account-container-name:}
18+
storage:
19+
blob:
20+
container-name: ${storage-account-container-name}
2021

2122
management:
2223
server:

0 commit comments

Comments
 (0)