|
51 | 51 | import io.minio.messages.SseConfiguration; |
52 | 52 | import io.minio.messages.Tags; |
53 | 53 | import io.minio.messages.VersioningConfiguration; |
| 54 | +import java.io.BufferedOutputStream; |
| 55 | +import java.io.ByteArrayOutputStream; |
| 56 | +import java.io.FileOutputStream; |
54 | 57 | import java.io.IOException; |
55 | 58 | import java.io.InputStream; |
56 | 59 | import java.io.OutputStream; |
|
64 | 67 | import java.nio.file.StandardOpenOption; |
65 | 68 | import java.security.InvalidKeyException; |
66 | 69 | import java.security.NoSuchAlgorithmException; |
| 70 | +import java.util.Date; |
67 | 71 | import java.util.Iterator; |
68 | 72 | import java.util.LinkedList; |
69 | 73 | import java.util.List; |
|
73 | 77 | import okhttp3.OkHttpClient; |
74 | 78 | import okhttp3.Request; |
75 | 79 | import okhttp3.Response; |
| 80 | +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; |
| 81 | +import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; |
| 82 | +import org.xerial.snappy.SnappyFramedOutputStream; |
76 | 83 |
|
77 | 84 | /** |
78 | 85 | * Simple Storage Service (aka S3) client to perform bucket and object operations. |
@@ -2591,6 +2598,131 @@ public void deleteObjectTags(DeleteObjectTagsArgs args) |
2591 | 2598 | executeDelete(args, null, queryParams); |
2592 | 2599 | } |
2593 | 2600 |
|
| 2601 | + /** |
| 2602 | + * Uploads multiple objects in a single put call. It is done by creating intermediate TAR file |
| 2603 | + * optionally compressed which is uploaded to S3 service. |
| 2604 | + * |
| 2605 | + * <pre>Example:{@code |
| 2606 | + * // Upload snowball objects. |
| 2607 | + * List<SnowballObject> objects = new ArrayList<SnowballObject>(); |
| 2608 | + * objects.add( |
| 2609 | + * new SnowballObject( |
| 2610 | + * "my-object-one", |
| 2611 | + * new ByteArrayInputStream("hello".getBytes(StandardCharsets.UTF_8)), |
| 2612 | + * 5, |
| 2613 | + * null)); |
| 2614 | + * objects.add( |
| 2615 | + * new SnowballObject( |
| 2616 | + * "my-object-two", |
| 2617 | + * new ByteArrayInputStream("java".getBytes(StandardCharsets.UTF_8)), |
| 2618 | + * 4, |
| 2619 | + * null)); |
| 2620 | + * minioClient.uploadSnowballObjects( |
| 2621 | + * UploadSnowballObjectsArgs.builder().bucket("my-bucketname").objects(objects).build()); |
| 2622 | + * }</pre> |
| 2623 | + * |
| 2624 | + * @param args {@link UploadSnowballObjectsArgs} object. |
| 2625 | + * @throws ErrorResponseException thrown to indicate S3 service returned an error response. |
| 2626 | + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. |
| 2627 | + * @throws InternalException thrown to indicate internal library error. |
| 2628 | + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. |
| 2629 | + * @throws InvalidResponseException thrown to indicate S3 service returned invalid or no error |
| 2630 | + * response. |
| 2631 | + * @throws IOException thrown to indicate I/O error on S3 operation. |
| 2632 | + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. |
| 2633 | + * @throws XmlParserException thrown to indicate XML parsing error. |
| 2634 | + */ |
| 2635 | + public ObjectWriteResponse uploadSnowballObjects(UploadSnowballObjectsArgs args) |
| 2636 | + throws ErrorResponseException, InsufficientDataException, InternalException, |
| 2637 | + InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException, |
| 2638 | + ServerException, XmlParserException { |
| 2639 | + checkArgs(args); |
| 2640 | + |
| 2641 | + FileOutputStream fos = null; |
| 2642 | + BufferedOutputStream bos = null; |
| 2643 | + SnappyFramedOutputStream sos = null; |
| 2644 | + ByteArrayOutputStream baos = null; |
| 2645 | + TarArchiveOutputStream tarOutputStream = null; |
| 2646 | + |
| 2647 | + try { |
| 2648 | + OutputStream os = null; |
| 2649 | + if (args.stagingFilename() != null) { |
| 2650 | + fos = new FileOutputStream(args.stagingFilename()); |
| 2651 | + bos = new BufferedOutputStream(fos); |
| 2652 | + os = bos; |
| 2653 | + } else { |
| 2654 | + baos = new ByteArrayOutputStream(); |
| 2655 | + os = baos; |
| 2656 | + } |
| 2657 | + |
| 2658 | + if (args.compression()) { |
| 2659 | + sos = new SnappyFramedOutputStream(os); |
| 2660 | + os = sos; |
| 2661 | + } |
| 2662 | + |
| 2663 | + tarOutputStream = new TarArchiveOutputStream(os); |
| 2664 | + for (SnowballObject object : args.objects()) { |
| 2665 | + if (object.filename() != null) { |
| 2666 | + Path filePath = Paths.get(object.filename()); |
| 2667 | + TarArchiveEntry entry = new TarArchiveEntry(filePath.toFile(), object.name()); |
| 2668 | + tarOutputStream.putArchiveEntry(entry); |
| 2669 | + Files.copy(filePath, tarOutputStream); |
| 2670 | + } else { |
| 2671 | + TarArchiveEntry entry = new TarArchiveEntry(object.name()); |
| 2672 | + if (object.modificationTime() != null) { |
| 2673 | + entry.setModTime(Date.from(object.modificationTime().toInstant())); |
| 2674 | + } |
| 2675 | + entry.setSize(object.size()); |
| 2676 | + tarOutputStream.putArchiveEntry(entry); |
| 2677 | + ByteStreams.copy(object.stream(), tarOutputStream); |
| 2678 | + } |
| 2679 | + tarOutputStream.closeArchiveEntry(); |
| 2680 | + } |
| 2681 | + tarOutputStream.finish(); |
| 2682 | + } finally { |
| 2683 | + if (tarOutputStream != null) tarOutputStream.flush(); |
| 2684 | + if (sos != null) sos.flush(); |
| 2685 | + if (bos != null) bos.flush(); |
| 2686 | + if (fos != null) fos.flush(); |
| 2687 | + if (tarOutputStream != null) tarOutputStream.close(); |
| 2688 | + if (sos != null) sos.close(); |
| 2689 | + if (bos != null) bos.close(); |
| 2690 | + if (fos != null) fos.close(); |
| 2691 | + } |
| 2692 | + |
| 2693 | + Multimap<String, String> headers = newMultimap(args.extraHeaders()); |
| 2694 | + headers.putAll(args.genHeaders()); |
| 2695 | + headers.put("X-Amz-Meta-Snowball-Auto-Extract", "true"); |
| 2696 | + |
| 2697 | + if (args.stagingFilename() == null) { |
| 2698 | + byte[] data = baos.toByteArray(); |
| 2699 | + return putObject( |
| 2700 | + args.bucket(), |
| 2701 | + args.region(), |
| 2702 | + args.object(), |
| 2703 | + data, |
| 2704 | + data.length, |
| 2705 | + headers, |
| 2706 | + args.extraQueryParams()); |
| 2707 | + } |
| 2708 | + |
| 2709 | + long length = Paths.get(args.stagingFilename()).toFile().length(); |
| 2710 | + if (length > ObjectWriteArgs.MAX_OBJECT_SIZE) { |
| 2711 | + throw new IllegalArgumentException( |
| 2712 | + "tarball size " + length + " is more than maximum allowed 5TiB"); |
| 2713 | + } |
| 2714 | + try (RandomAccessFile file = new RandomAccessFile(args.stagingFilename(), "r")) { |
| 2715 | + return putObject( |
| 2716 | + args.bucket(), |
| 2717 | + args.region(), |
| 2718 | + args.object(), |
| 2719 | + file, |
| 2720 | + length, |
| 2721 | + headers, |
| 2722 | + args.extraQueryParams()); |
| 2723 | + } |
| 2724 | + } |
| 2725 | + |
2594 | 2726 | public static Builder builder() { |
2595 | 2727 | return new Builder(); |
2596 | 2728 | } |
|
0 commit comments