1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.waarp.openr66.s3;
21
22 import com.google.common.base.Function;
23 import com.google.common.collect.Iterators;
24 import io.minio.BucketExistsArgs;
25 import io.minio.GetObjectArgs;
26 import io.minio.GetObjectRetentionArgs;
27 import io.minio.GetObjectTagsArgs;
28 import io.minio.ListObjectsArgs;
29 import io.minio.MakeBucketArgs;
30 import io.minio.MinioClient;
31 import io.minio.ObjectWriteResponse;
32 import io.minio.RemoveObjectArgs;
33 import io.minio.Result;
34 import io.minio.SetObjectRetentionArgs;
35 import io.minio.SetObjectTagsArgs;
36 import io.minio.UploadObjectArgs;
37 import io.minio.errors.MinioException;
38 import io.minio.messages.Item;
39 import io.minio.messages.Retention;
40 import io.minio.messages.RetentionMode;
41 import io.minio.messages.Tags;
42 import org.checkerframework.checker.nullness.qual.Nullable;
43 import org.waarp.common.file.FileUtils;
44 import org.waarp.common.logging.WaarpLogger;
45 import org.waarp.common.logging.WaarpLoggerFactory;
46 import org.waarp.common.utility.ParametersChecker;
47 import org.waarp.common.utility.SingletonUtils;
48 import org.waarp.openr66.protocol.exception.OpenR66ProtocolNetworkException;
49
50 import java.io.File;
51 import java.io.FileOutputStream;
52 import java.io.IOException;
53 import java.io.InputStream;
54 import java.net.URL;
55 import java.security.InvalidKeyException;
56 import java.security.NoSuchAlgorithmException;
57 import java.time.ZonedDateTime;
58 import java.util.Iterator;
59 import java.util.Map;
60
61 import static org.waarp.common.file.FileUtils.*;
62
63
64
65
66 public class WaarpR66S3Client {
67 private static final WaarpLogger logger =
68 WaarpLoggerFactory.getLogger(WaarpR66S3Client.class);
69 public static final String BUCKET_OR_TARGET_CANNOT_BE_NULL_OR_EMPTY =
70 "Bucket or Target cannot be null or empty";
71 public static final String S_3_ISSUE = "S3 issue: ";
72 public static final String BUCKET_OR_SOURCE_CANNOT_BE_NULL_OR_EMPTY =
73 "Bucket or Source cannot be null or empty";
74
75 private static final Function<Result<Item>, String> function =
76 new Function<Result<Item>, String>() {
77 @Override
78 public final @Nullable String apply(
79 @Nullable final Result<Item> itemResult) {
80 try {
81 if (itemResult != null) {
82 final Item item = itemResult.get();
83 if (item != null) {
84 return item.objectName();
85 }
86 }
87 return null;
88 } catch (final MinioException | InvalidKeyException | IOException |
89 NoSuchAlgorithmException e) {
90 logger.error(e.getMessage());
91 return null;
92 }
93 }
94 };
95 private final MinioClient minioClient;
96
97
98
99
100
101
102
103
104 public WaarpR66S3Client(final String accessKey, final String secretKey,
105 final URL endPointS3) {
106 ParametersChecker.checkParameter("Parameters cannot be null or empty",
107 accessKey, secretKey, endPointS3);
108
109 minioClient = MinioClient.builder().endpoint(endPointS3)
110 .credentials(accessKey, secretKey).build();
111 }
112
113
114
115
116
117
118
119
120
121
122
123
124
125 public final String createFile(final String bucketName,
126 final String targetName, final File file,
127 final Map<String, String> tags)
128 throws OpenR66ProtocolNetworkException {
129 ParametersChecker.checkParameter(BUCKET_OR_TARGET_CANNOT_BE_NULL_OR_EMPTY,
130 bucketName, targetName);
131 ParametersChecker.checkParameter("File cannot be null", file);
132 if (!file.canRead()) {
133 throw new IllegalArgumentException(
134 "File cannot be read: " + file.getAbsolutePath());
135 }
136 boolean uploaded = false;
137 boolean error = false;
138 try {
139
140 final boolean found = minioClient.bucketExists(
141 BucketExistsArgs.builder().bucket(bucketName).build());
142 if (!found) {
143
144 minioClient.makeBucket(
145 MakeBucketArgs.builder().bucket(bucketName).build());
146 } else {
147 logger.info("Bucket {} already exists.", bucketName);
148 }
149
150 final ObjectWriteResponse response = minioClient.uploadObject(
151 UploadObjectArgs.builder().bucket(bucketName).object(targetName)
152 .filename(file.getAbsolutePath()).build());
153 uploaded = true;
154 logger.info("{} is successfully uploaded as object {} to bucket {}.",
155 file.getAbsolutePath(), targetName, bucketName);
156 if (tags != null && !tags.isEmpty()) {
157 minioClient.setObjectTags(
158 SetObjectTagsArgs.builder().bucket(bucketName).object(targetName)
159 .tags(tags).build());
160 }
161 logger.debug("Resp: {} {} {} {} {}", response.bucket(), response.object(),
162 response.versionId(), response.etag(), response.region());
163 return response.versionId();
164 } catch (final MinioException | IOException | NoSuchAlgorithmException |
165 InvalidKeyException e) {
166 logger.error(e.getMessage());
167 error = true;
168 throw new OpenR66ProtocolNetworkException(S_3_ISSUE + e.getMessage(), e);
169 } finally {
170 if (error && uploaded) {
171
172 try {
173 deleteFile(bucketName, targetName);
174 } catch (final Exception e) {
175 logger.warn(
176 "Error while cleaning S3 file incompletely created" + " : {}",
177 e.getMessage());
178 }
179 }
180 }
181 }
182
183
184
185
186
187
188
189
190
191
192 public final void setTags(final String bucketName, final String targetName,
193 final Map<String, String> tags)
194 throws OpenR66ProtocolNetworkException {
195 ParametersChecker.checkParameter(BUCKET_OR_TARGET_CANNOT_BE_NULL_OR_EMPTY,
196 bucketName, targetName);
197 try {
198 if (tags != null && !tags.isEmpty()) {
199 minioClient.setObjectTags(
200 SetObjectTagsArgs.builder().bucket(bucketName).object(targetName)
201 .tags(tags).build());
202 }
203 } catch (final MinioException | IOException | NoSuchAlgorithmException |
204 InvalidKeyException e) {
205 logger.error(e.getMessage());
206 throw new OpenR66ProtocolNetworkException(S_3_ISSUE + e.getMessage(), e);
207 }
208 }
209
210
211
212
213
214
215
216
217
218
219
220 public final ZonedDateTime getObjectRetention(final String bucketName,
221 final String sourceName)
222 throws OpenR66ProtocolNetworkException {
223 ParametersChecker.checkParameter(BUCKET_OR_TARGET_CANNOT_BE_NULL_OR_EMPTY,
224 bucketName, sourceName);
225 try {
226 final Retention retention = minioClient.getObjectRetention(
227 GetObjectRetentionArgs.builder().bucket(bucketName).object(bucketName)
228 .build());
229 return retention.retainUntilDate();
230 } catch (final MinioException | InvalidKeyException | IOException |
231 NoSuchAlgorithmException e) {
232 logger.error(e.getMessage());
233 throw new OpenR66ProtocolNetworkException(S_3_ISSUE + e.getMessage(), e);
234 }
235 }
236
237
238
239
240
241
242
243
244
245
246 public final void bypassObjectRetention(final String bucketName,
247 final String targetName,
248 final ZonedDateTime retentionUntil)
249 throws OpenR66ProtocolNetworkException {
250 ParametersChecker.checkParameter(BUCKET_OR_TARGET_CANNOT_BE_NULL_OR_EMPTY,
251 bucketName, targetName);
252 ParametersChecker.checkParameter("Retention cannot be null",
253 retentionUntil);
254 if (retentionUntil.isBefore(ZonedDateTime.now())) {
255 logger.warn("Retention Date Time is before now");
256 throw new IllegalArgumentException("Retention Date Time is before now");
257 }
258 final Retention config =
259 new Retention(RetentionMode.COMPLIANCE, retentionUntil);
260
261 try {
262 minioClient.setObjectRetention(
263 SetObjectRetentionArgs.builder().bucket(bucketName).object(targetName)
264 .config(config).bypassGovernanceMode(true)
265 .build());
266 } catch (final MinioException | InvalidKeyException | IOException |
267 NoSuchAlgorithmException e) {
268 logger.error(e.getMessage());
269 throw new OpenR66ProtocolNetworkException(S_3_ISSUE + e.getMessage(), e);
270 }
271 }
272
273
274
275
276
277
278
279
280
281
282
283
284
285 public final Map<String, String> getFile(final String bucketName,
286 final String sourceName,
287 final File file,
288 final boolean getTags)
289 throws OpenR66ProtocolNetworkException {
290 ParametersChecker.checkParameter(BUCKET_OR_SOURCE_CANNOT_BE_NULL_OR_EMPTY,
291 bucketName, sourceName);
292 ParametersChecker.checkParameter("File cannot be null", file);
293 boolean downloaded = false;
294 boolean error = false;
295
296 try (final InputStream stream = minioClient.getObject(
297 GetObjectArgs.builder().bucket(bucketName).object(sourceName).build());
298 final FileOutputStream outputStream = new FileOutputStream(file)) {
299 final byte[] buf = new byte[ZERO_COPY_CHUNK_SIZE];
300 int bytesRead;
301 while ((bytesRead = stream.read(buf, 0, buf.length)) >= 0) {
302 outputStream.write(buf, 0, bytesRead);
303 }
304 FileUtils.close(outputStream);
305 FileUtils.close(stream);
306 downloaded = true;
307 if (getTags) {
308 final Tags tags = minioClient.getObjectTags(
309 GetObjectTagsArgs.builder().bucket(bucketName).object(sourceName)
310 .build());
311 return tags.get();
312 } else {
313 return SingletonUtils.singletonMap();
314 }
315 } catch (final MinioException | IOException | NoSuchAlgorithmException |
316 InvalidKeyException e) {
317 logger.info(e);
318 error = true;
319 throw new OpenR66ProtocolNetworkException(S_3_ISSUE + e.getMessage(), e);
320 } finally {
321 if (error && downloaded) {
322
323 file.delete();
324 }
325 }
326 }
327
328
329
330
331
332
333
334
335
336
337
338 public final Map<String, String> getTags(final String bucketName,
339 final String sourceName)
340 throws OpenR66ProtocolNetworkException {
341 ParametersChecker.checkParameter(BUCKET_OR_SOURCE_CANNOT_BE_NULL_OR_EMPTY,
342 bucketName, sourceName);
343 try {
344 final Tags tags = minioClient.getObjectTags(
345 GetObjectTagsArgs.builder().bucket(bucketName).object(sourceName)
346 .build());
347
348 return tags.get();
349 } catch (final MinioException | IOException | NoSuchAlgorithmException |
350 InvalidKeyException e) {
351 logger.error(e.getMessage());
352 throw new OpenR66ProtocolNetworkException(S_3_ISSUE + e.getMessage(), e);
353 }
354 }
355
356
357
358
359
360
361
362
363
364 public final void deleteFile(final String bucketName, final String sourceName)
365 throws OpenR66ProtocolNetworkException {
366 ParametersChecker.checkParameter(BUCKET_OR_SOURCE_CANNOT_BE_NULL_OR_EMPTY,
367 bucketName, sourceName);
368 try {
369
370 minioClient.removeObject(
371 RemoveObjectArgs.builder().bucket(bucketName).object(sourceName)
372 .build());
373 } catch (final MinioException | IOException | NoSuchAlgorithmException |
374 InvalidKeyException e) {
375 logger.error(e.getMessage());
376 throw new OpenR66ProtocolNetworkException(S_3_ISSUE + e.getMessage(), e);
377 }
378 }
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393 public final Iterator<String> listObjectsFromBucket(final String bucketName,
394 final String optionalNameStartWith,
395 final boolean recursively,
396 final int limit)
397 throws OpenR66ProtocolNetworkException {
398 ParametersChecker.checkParameter("Bucket cannot be null or empty",
399 bucketName);
400 try {
401
402 final ListObjectsArgs.Builder builder =
403 ListObjectsArgs.builder().bucket(bucketName).recursive(recursively);
404 if (ParametersChecker.isNotEmpty(optionalNameStartWith)) {
405 builder.prefix(optionalNameStartWith);
406 }
407 if (limit > 0) {
408 builder.maxKeys(limit);
409 }
410 final ListObjectsArgs args = builder.build();
411 final Iterable<Result<Item>> iterable = minioClient.listObjects(args);
412 return Iterators.transform(iterable.iterator(), function);
413 } catch (final Exception e) {
414 logger.error(e.getMessage());
415 throw new OpenR66ProtocolNetworkException(S_3_ISSUE + e.getMessage(), e);
416 }
417 }
418 }