1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.bremersee.data.minio;
18
19 import io.minio.BucketExistsArgs;
20 import io.minio.GetBucketVersioningArgs;
21 import io.minio.GetPresignedObjectUrlArgs;
22 import io.minio.ListObjectsArgs;
23 import io.minio.MakeBucketArgs;
24 import io.minio.MinioClient;
25 import io.minio.ObjectWriteResponse;
26 import io.minio.PutObjectArgs;
27 import io.minio.RemoveObjectArgs;
28 import io.minio.RemoveObjectsArgs;
29 import io.minio.Result;
30 import io.minio.SetBucketVersioningArgs;
31 import io.minio.StatObjectArgs;
32 import io.minio.StatObjectResponse;
33 import io.minio.http.Method;
34 import io.minio.messages.DeleteError;
35 import io.minio.messages.DeleteObject;
36 import io.minio.messages.Item;
37 import io.minio.messages.VersioningConfiguration;
38 import io.minio.messages.VersioningConfiguration.Status;
39 import java.io.IOException;
40 import java.io.InputStream;
41 import java.time.Duration;
42 import java.time.temporal.ChronoUnit;
43 import java.util.ArrayList;
44 import java.util.Collection;
45 import java.util.Collections;
46 import java.util.List;
47 import java.util.Optional;
48 import java.util.stream.Collectors;
49 import lombok.Getter;
50 import org.bremersee.exception.ServiceException;
51 import org.bremersee.web.multipart.FileAwareMultipartFile;
52 import org.springframework.http.MediaType;
53 import org.springframework.util.StringUtils;
54 import org.springframework.web.multipart.MultipartFile;
55
56
57
58
59
60
61 public class MinioRepositoryImpl implements MinioRepository {
62
63 private final MinioOperations minio;
64
65 @Getter
66 private final String region;
67
68 @Getter
69 private final String bucket;
70
71 private final boolean enableVersioning;
72
73 private final Duration presignedObjectUrlDuration;
74
75
76
77
78
79
80
81
82
83
84
85 public MinioRepositoryImpl(
86 MinioOperations minioOperations,
87 String region,
88 String bucket,
89 boolean enableVersioning,
90 boolean create,
91 Duration presignedObjectUrlDuration) {
92
93 this.minio = minioOperations;
94 this.region = region;
95 this.bucket = bucket;
96 this.enableVersioning = enableVersioning;
97 this.presignedObjectUrlDuration = validateDuration(presignedObjectUrlDuration);
98 if (create) {
99 createBucket();
100 }
101 }
102
103
104
105
106
107
108
109
110
111
112
113 public MinioRepositoryImpl(
114 MinioClient minioClient,
115 String region,
116 String bucket,
117 boolean enableVersioning,
118 boolean create,
119 Duration presignedObjectUrlDuration) {
120
121 this(
122 new MinioTemplate(minioClient),
123 region,
124 bucket,
125 enableVersioning,
126 create,
127 presignedObjectUrlDuration);
128 }
129
130 private static Duration validateDuration(Duration presignedObjectUrlDuration) {
131 return Optional.ofNullable(presignedObjectUrlDuration)
132 .filter(duration -> duration.toMillis() > 1000L * 30L
133 && duration.toMillis() < 1000L * 60L * 60L * 24L * 7L)
134 .orElseGet(() -> Duration.of(1L, ChronoUnit.DAYS));
135 }
136
137 private void createBucket() {
138 if (!minio.bucketExists(BucketExistsArgs.builder().bucket(bucket).build())) {
139 minio.makeBucket(MakeBucketArgs.builder()
140 .region(region)
141 .bucket(bucket)
142 .build());
143 }
144 VersioningConfiguration versioningConfiguration = minio.getBucketVersioning(GetBucketVersioningArgs.builder()
145 .region(region)
146 .bucket(bucket)
147 .build());
148 switch (versioningConfiguration.status()) {
149 case ENABLED:
150 if (!enableVersioning) {
151 minio.setBucketVersioning(SetBucketVersioningArgs.builder()
152 .region(region)
153 .bucket(bucket)
154 .config(new VersioningConfiguration(Status.SUSPENDED, false))
155 .build());
156 }
157 break;
158 case SUSPENDED:
159 case OFF:
160 if (enableVersioning) {
161 minio.setBucketVersioning(SetBucketVersioningArgs.builder()
162 .region(region)
163 .bucket(bucket)
164 .config(new VersioningConfiguration(Status.ENABLED, false))
165 .build());
166 }
167 break;
168 default:
169 }
170 }
171
172 @Override
173 public MinioOperations getMinioOperations() {
174 return minio;
175 }
176
177 @Override
178 public boolean isVersioningEnabled() {
179 return enableVersioning;
180 }
181
182 @Override
183 public Optional<ObjectWriteResponse> save(
184 MinioObjectId id,
185 MultipartFile multipartFile,
186 DeleteMode deleteMode) {
187
188 return Optional.ofNullable(multipartFile)
189 .filter(file -> !file.isEmpty())
190 .map(file -> {
191 try (InputStream in = file.getInputStream()) {
192 ObjectWriteResponse response = minio.putObject(PutObjectArgs.builder()
193 .contentType(StringUtils.hasText(file.getContentType())
194 ? file.getContentType()
195 : MediaType.APPLICATION_OCTET_STREAM_VALUE)
196 .stream(in, file.getSize(), -1)
197 .region(region)
198 .bucket(bucket)
199 .object(id.getName())
200 .build());
201 if (DeleteMode.ON_SUCCESS == deleteMode) {
202 FileAwareMultipartFile.delete(file);
203 }
204 return response;
205
206 } catch (IOException e) {
207 throw ServiceException.internalServerError("Put object failed.", e);
208
209 } finally {
210 if (DeleteMode.ALWAYS == deleteMode) {
211 FileAwareMultipartFile.delete(file);
212 }
213 }
214 });
215 }
216
217 @Override
218 public boolean exists(MinioObjectId id) {
219 return minio.objectExists(StatObjectArgs.builder()
220 .region(region)
221 .bucket(bucket)
222 .object(id.getName())
223 .versionId(id.getVersionId())
224 .build());
225 }
226
227 @Override
228 public Optional<MinioMultipartFile> findOne(MinioObjectId id) {
229 try {
230 StatObjectResponse objectStat = minio.statObject(StatObjectArgs.builder()
231 .region(region)
232 .bucket(bucket)
233 .object(id.getName())
234 .versionId(id.getVersionId())
235 .build());
236 return Optional.of(new MinioMultipartFileImpl(minio, region, objectStat));
237
238 } catch (MinioException e) {
239 if (404 == e.status()) {
240 return Optional.empty();
241 }
242 throw e;
243 }
244 }
245
246 @Override
247 public List<MinioMultipartFile> findAll(String prefix) {
248 Iterable<Result<Item>> results = minio.listObjects(ListObjectsArgs.builder()
249 .region(region)
250 .bucket(bucket)
251 .includeVersions(enableVersioning)
252 .recursive(true)
253 .prefix(prefix)
254 .build());
255 List<MinioMultipartFile> fileList = new ArrayList<>();
256 for (Result<Item> result : results) {
257 try {
258 Item item = result.get();
259 if (!item.isDir() && !item.isDeleteMarker()) {
260 fileList.add(new MinioMultipartFileImpl(
261 getMinioOperations(),
262 region,
263 bucket,
264 item));
265 }
266 } catch (Exception ignored) {
267
268 }
269 }
270 return fileList;
271 }
272
273 @Override
274 public void delete(MinioObjectId id) {
275 minio.removeObject(RemoveObjectArgs.builder()
276 .region(region)
277 .bucket(bucket)
278 .object(id.getName())
279 .versionId(id.getVersionId())
280 .build());
281 }
282
283 @Override
284 public List<DeleteError> deleteAll(Collection<MinioObjectId> ids) {
285
286 if (ids == null || ids.isEmpty()) {
287 return Collections.emptyList();
288 }
289 Iterable<Result<DeleteError>> errors = minio.removeObjects(RemoveObjectsArgs.builder()
290 .region(region)
291 .bucket(bucket)
292 .objects(ids.stream()
293 .map(id -> new DeleteObject(id.getName(), id.getVersionId()))
294 .collect(Collectors.toSet()))
295 .build());
296 List<DeleteError> errorList = new ArrayList<>();
297 for (Result<DeleteError> error : errors) {
298 try {
299 errorList.add(error.get());
300 } catch (Exception ignored) {
301
302 }
303 }
304 return errorList;
305 }
306
307 @Override
308 public String getPresignedObjectUrl(MinioObjectId id, Method method, Duration duration) {
309 Duration expiry = duration != null ? validateDuration(duration) : presignedObjectUrlDuration;
310 return minio.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
311 .expiry((int) expiry.toSeconds())
312 .method(method)
313 .region(region)
314 .bucket(bucket)
315 .object(id.getName())
316 .versionId(id.getVersionId())
317 .build());
318 }
319
320 }