From 97a19a2d1705dee7b7e0d420b01609387928631c Mon Sep 17 00:00:00 2001 From: ZhouXY108 Date: Sun, 22 Dec 2024 15:40:42 +0800 Subject: [PATCH] commit. --- .editorconfig | 15 + .gitignore | 40 + plusone-oss-spring-boot2-test/pom.xml | 110 + .../main/java/io/minio/MinioAsyncClient.java | 3373 +++++++++++++++++ .../java/xyz/zhouxy/plusone/oss/OSSType.java | 32 + .../oss/PlusoneOssTestApplication.java | 11 + .../oss/PlusoneS3AutoConfiguration.java | 152 + .../java/xyz/zhouxy/plusone/oss/Protocol.java | 66 + .../xyz/zhouxy/plusone/oss/S3Properties.java | 143 + ...itional-spring-configuration-metadata.json | 37 + .../main/resources/META-INF/spring.factories | 2 + .../src/main/resources/application.yaml | 23 + .../oss/PlusoneOssTestApplicationTests.java | 56 + pom.xml | 22 + 14 files changed, 4082 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 plusone-oss-spring-boot2-test/pom.xml create mode 100644 plusone-oss-spring-boot2-test/src/main/java/io/minio/MinioAsyncClient.java create mode 100644 plusone-oss-spring-boot2-test/src/main/java/xyz/zhouxy/plusone/oss/OSSType.java create mode 100644 plusone-oss-spring-boot2-test/src/main/java/xyz/zhouxy/plusone/oss/PlusoneOssTestApplication.java create mode 100644 plusone-oss-spring-boot2-test/src/main/java/xyz/zhouxy/plusone/oss/PlusoneS3AutoConfiguration.java create mode 100644 plusone-oss-spring-boot2-test/src/main/java/xyz/zhouxy/plusone/oss/Protocol.java create mode 100644 plusone-oss-spring-boot2-test/src/main/java/xyz/zhouxy/plusone/oss/S3Properties.java create mode 100644 plusone-oss-spring-boot2-test/src/main/resources/META-INF/additional-spring-configuration-metadata.json create mode 100644 plusone-oss-spring-boot2-test/src/main/resources/META-INF/spring.factories create mode 100644 plusone-oss-spring-boot2-test/src/main/resources/application.yaml create mode 100644 plusone-oss-spring-boot2-test/src/test/java/xyz/zhouxy/plusone/oss/PlusoneOssTestApplicationTests.java create mode 100644 pom.xml diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e795376 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.yaml] +indent_size = 2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c627433 --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ +*/**/http/*.http + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### bak ### +*.bak +**/resources/*-dev.* +**/resources/*-sit.* +**/resources/*-uat.* diff --git a/plusone-oss-spring-boot2-test/pom.xml b/plusone-oss-spring-boot2-test/pom.xml new file mode 100644 index 0000000..205b837 --- /dev/null +++ b/plusone-oss-spring-boot2-test/pom.xml @@ -0,0 +1,110 @@ + + + 4.0.0 + + xyz.zhouxy.plusone.oss + plusone-oss + 1.0-SNAPSHOT + + + xyz.zhouxy.plusone.oss + plusone-oss-spring-boot2-test + 1.0-SNAPSHOT + + + 17 + 17 + 17 + UTF-8 + UTF-8 + 2.7.18 + 2.29.36 + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + org.projectlombok + lombok + true + + + + io.minio + minio + 8.5.14 + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + software.amazon.awssdk + bom + ${aws.java.sdk.version} + pom + import + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 17 + 17 + UTF-8 + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + xyz.zhouxy.plusone.oss.PlusoneOssTestApplication + + true + + + + repackage + + repackage + + + + + + + diff --git a/plusone-oss-spring-boot2-test/src/main/java/io/minio/MinioAsyncClient.java b/plusone-oss-spring-boot2-test/src/main/java/io/minio/MinioAsyncClient.java new file mode 100644 index 0000000..4e281d0 --- /dev/null +++ b/plusone-oss-spring-boot2-test/src/main/java/io/minio/MinioAsyncClient.java @@ -0,0 +1,3373 @@ +/* + * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, + * (C) 2022 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.minio; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.google.common.io.ByteStreams; +import io.minio.credentials.Credentials; +import io.minio.credentials.Provider; +import io.minio.credentials.StaticProvider; +import io.minio.errors.BucketPolicyTooLargeException; +import io.minio.errors.ErrorResponseException; +import io.minio.errors.InsufficientDataException; +import io.minio.errors.InternalException; +import io.minio.errors.InvalidResponseException; +import io.minio.errors.ServerException; +import io.minio.errors.XmlParserException; +import io.minio.http.HttpUtils; +import io.minio.http.Method; +import io.minio.messages.Bucket; +import io.minio.messages.CopyObjectResult; +import io.minio.messages.CreateBucketConfiguration; +import io.minio.messages.DeleteError; +import io.minio.messages.DeleteObject; +import io.minio.messages.Item; +import io.minio.messages.LegalHold; +import io.minio.messages.LifecycleConfiguration; +import io.minio.messages.ListAllMyBucketsResult; +import io.minio.messages.NotificationConfiguration; +import io.minio.messages.NotificationRecords; +import io.minio.messages.ObjectLockConfiguration; +import io.minio.messages.Part; +import io.minio.messages.ReplicationConfiguration; +import io.minio.messages.Retention; +import io.minio.messages.SelectObjectContentRequest; +import io.minio.messages.SseConfiguration; +import io.minio.messages.Tags; +import io.minio.messages.VersioningConfiguration; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Date; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutionException; +import java.util.regex.Matcher; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; +import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; +import org.xerial.snappy.SnappyFramedOutputStream; + +/** + * Simple Storage Service (aka S3) client to perform bucket and object operations asynchronously. + * + *

Bucket operations

+ * + * + * + *

Object operations

+ * + * + * + *

If access/secret keys are provided, all S3 operation requests are signed using AWS Signature + * Version 4; else they are performed anonymously. + * + *

Examples on using this library are available here. + * + *

Use {@code MinioAsyncClient.builder()} to create S3 client. + * + *

{@code
+ * // Create client with anonymous access.
+ * MinioAsyncClient minioAsyncClient =
+ *     MinioAsyncClient.builder().endpoint("https://play.min.io").build();
+ *
+ * // Create client with credentials.
+ * MinioAsyncClient minioAsyncClient =
+ *     MinioAsyncClient.builder()
+ *         .endpoint("https://play.min.io")
+ *         .credentials("Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG")
+ *         .build();
+ * }
+ */ +public class MinioAsyncClient extends S3Base { + private MinioAsyncClient( + HttpUrl baseUrl, + String awsS3Prefix, + String awsDomainSuffix, + boolean awsDualstack, + boolean useVirtualStyle, + String region, + Provider provider, + OkHttpClient httpClient, + boolean closeHttpClient) { + super( + baseUrl, + awsS3Prefix, + awsDomainSuffix, + awsDualstack, + useVirtualStyle, + region, + provider, + httpClient, + closeHttpClient); + } + + protected MinioAsyncClient(MinioAsyncClient client) { + super(client); + } + + /** + * Gets information of an object asynchronously. + * + *
Example:{@code
+   * // Get information of an object.
+   * CompletableFuture future =
+   *     minioAsyncClient.statObject(
+   *         StatObjectArgs.builder().bucket("my-bucketname").object("my-objectname").build());
+   *
+   * // Get information of SSE-C encrypted object.
+   * CompletableFuture future =
+   *     minioAsyncClient.statObject(
+   *         StatObjectArgs.builder()
+   *             .bucket("my-bucketname")
+   *             .object("my-objectname")
+   *             .ssec(ssec)
+   *             .build());
+   *
+   * // Get information of a versioned object.
+   * CompletableFuture future =
+   *     minioAsyncClient.statObject(
+   *         StatObjectArgs.builder()
+   *             .bucket("my-bucketname")
+   *             .object("my-objectname")
+   *             .versionId("version-id")
+   *             .build());
+   *
+   * // Get information of a SSE-C encrypted versioned object.
+   * CompletableFuture future =
+   *     minioAsyncClient.statObject(
+   *         StatObjectArgs.builder()
+   *             .bucket("my-bucketname")
+   *             .object("my-objectname")
+   *             .versionId("version-id")
+   *             .ssec(ssec)
+   *             .build());
+   * }
+ * + * @param args {@link StatObjectArgs} object. + * @return {@link CompletableFuture}<{@link StatObjectResponse}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + * @see StatObjectResponse + */ + public CompletableFuture statObject(StatObjectArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + return super.statObjectAsync(args); + } + + /** + * Gets data from offset to length of a SSE-C encrypted object asynchronously. + * + *
Example:{@code
+   * CompletableFuture future = minioAsyncClient.getObject(
+   *     GetObjectArgs.builder()
+   *         .bucket("my-bucketname")
+   *         .object("my-objectname")
+   *         .offset(offset)
+   *         .length(len)
+   *         .ssec(ssec)
+   *         .build()
+   * }
+ * + * @param args Object of {@link GetObjectArgs} + * @return {@link CompletableFuture}<{@link GetObjectResponse}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + * @see GetObjectResponse + */ + public CompletableFuture getObject(GetObjectArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + args.validateSsec(this.baseUrl); + return executeGetAsync( + args, + args.getHeaders(), + (args.versionId() != null) ? newMultimap("versionId", args.versionId()) : null) + .thenApply( + response -> { + return new GetObjectResponse( + response.headers(), + args.bucket(), + args.region(), + args.object(), + response.body().byteStream()); + }); + } + + private void downloadObject( + String filename, + boolean overwrite, + StatObjectResponse statObjectResponse, + GetObjectResponse getObjectResponse) + throws IOException { + OutputStream os = null; + try { + Path filePath = Paths.get(filename); + String tempFilename = + filename + "." + S3Escaper.encode(statObjectResponse.etag()) + ".part.minio"; + Path tempFilePath = Paths.get(tempFilename); + if (Files.exists(tempFilePath)) Files.delete(tempFilePath); + os = Files.newOutputStream(tempFilePath, StandardOpenOption.CREATE, StandardOpenOption.WRITE); + long bytesWritten = ByteStreams.copy(getObjectResponse, os); + if (bytesWritten != statObjectResponse.size()) { + throw new IOException( + tempFilename + + ": unexpected data written. expected = " + + statObjectResponse.size() + + ", written = " + + bytesWritten); + } + + if (overwrite) { + Files.move(tempFilePath, filePath, StandardCopyOption.REPLACE_EXISTING); + } else { + Files.move(tempFilePath, filePath); + } + } finally { + getObjectResponse.close(); + if (os != null) os.close(); + } + } + + /** + * Downloads data of a SSE-C encrypted object to file. + * + *
Example:{@code
+   * CompletableFuture future = minioAsyncClient.downloadObject(
+   *     DownloadObjectArgs.builder()
+   *         .bucket("my-bucketname")
+   *         .object("my-objectname")
+   *         .ssec(ssec)
+   *         .filename("my-filename")
+   *         .build());
+   * }
+ * + * @param args Object of {@link DownloadObjectArgs} + * @return {@link CompletableFuture}<{@link Void}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture downloadObject(DownloadObjectArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + String filename = args.filename(); + Path filePath = Paths.get(filename); + if (!args.overwrite() && Files.exists(filePath)) { + throw new IllegalArgumentException("Destination file " + filename + " already exists"); + } + + return statObjectAsync(new StatObjectArgs(args)) + .thenCombine( + getObject(new GetObjectArgs(args)), + (statObjectResponse, getObjectResponse) -> { + try { + downloadObject(filename, args.overwrite(), statObjectResponse, getObjectResponse); + return null; + } catch (IOException e) { + throw new CompletionException(e); + } + }) + .thenAccept(nullValue -> {}); + } + + /** + * Creates an object by server-side copying data from another object. + * + *
Example:{@code
+   * // Create object "my-objectname" in bucket "my-bucketname" by copying from object
+   * // "my-objectname" in bucket "my-source-bucketname".
+   * CompletableFuture future = minioAsyncClient.copyObject(
+   *     CopyObjectArgs.builder()
+   *         .bucket("my-bucketname")
+   *         .object("my-objectname")
+   *         .source(
+   *             CopySource.builder()
+   *                 .bucket("my-source-bucketname")
+   *                 .object("my-objectname")
+   *                 .build())
+   *         .build());
+   *
+   * // Create object "my-objectname" in bucket "my-bucketname" by copying from object
+   * // "my-source-objectname" in bucket "my-source-bucketname".
+   * CompletableFuture future = minioAsyncClient.copyObject(
+   *     CopyObjectArgs.builder()
+   *         .bucket("my-bucketname")
+   *         .object("my-objectname")
+   *         .source(
+   *             CopySource.builder()
+   *                 .bucket("my-source-bucketname")
+   *                 .object("my-source-objectname")
+   *                 .build())
+   *         .build());
+   *
+   * // Create object "my-objectname" in bucket "my-bucketname" with SSE-KMS server-side
+   * // encryption by copying from object "my-objectname" in bucket "my-source-bucketname".
+   * CompletableFuture future = minioAsyncClient.copyObject(
+   *     CopyObjectArgs.builder()
+   *         .bucket("my-bucketname")
+   *         .object("my-objectname")
+   *         .source(
+   *             CopySource.builder()
+   *                 .bucket("my-source-bucketname")
+   *                 .object("my-objectname")
+   *                 .build())
+   *         .sse(sseKms) // Replace with actual key.
+   *         .build());
+   *
+   * // Create object "my-objectname" in bucket "my-bucketname" with SSE-S3 server-side
+   * // encryption by copying from object "my-objectname" in bucket "my-source-bucketname".
+   * CompletableFuture future = minioAsyncClient.copyObject(
+   *     CopyObjectArgs.builder()
+   *         .bucket("my-bucketname")
+   *         .object("my-objectname")
+   *         .source(
+   *             CopySource.builder()
+   *                 .bucket("my-source-bucketname")
+   *                 .object("my-objectname")
+   *                 .build())
+   *         .sse(sseS3) // Replace with actual key.
+   *         .build());
+   *
+   * // Create object "my-objectname" in bucket "my-bucketname" with SSE-C server-side encryption
+   * // by copying from object "my-objectname" in bucket "my-source-bucketname".
+   * CompletableFuture future = minioAsyncClient.copyObject(
+   *     CopyObjectArgs.builder()
+   *         .bucket("my-bucketname")
+   *         .object("my-objectname")
+   *         .source(
+   *             CopySource.builder()
+   *                 .bucket("my-source-bucketname")
+   *                 .object("my-objectname")
+   *                 .build())
+   *         .sse(ssec) // Replace with actual key.
+   *         .build());
+   *
+   * // Create object "my-objectname" in bucket "my-bucketname" by copying from SSE-C encrypted
+   * // object "my-source-objectname" in bucket "my-source-bucketname".
+   * CompletableFuture future = minioAsyncClient.copyObject(
+   *     CopyObjectArgs.builder()
+   *         .bucket("my-bucketname")
+   *         .object("my-objectname")
+   *         .source(
+   *             CopySource.builder()
+   *                 .bucket("my-source-bucketname")
+   *                 .object("my-source-objectname")
+   *                 .ssec(ssec) // Replace with actual key.
+   *                 .build())
+   *         .build());
+   *
+   * // Create object "my-objectname" in bucket "my-bucketname" with custom headers conditionally
+   * // by copying from object "my-objectname" in bucket "my-source-bucketname".
+   * CompletableFuture future = minioAsyncClient.copyObject(
+   *     CopyObjectArgs.builder()
+   *         .bucket("my-bucketname")
+   *         .object("my-objectname")
+   *         .source(
+   *             CopySource.builder()
+   *                 .bucket("my-source-bucketname")
+   *                 .object("my-objectname")
+   *                 .matchETag(etag) // Replace with actual etag.
+   *                 .build())
+   *         .headers(headers) // Replace with actual headers.
+   *         .build());
+   * }
+ * + * @param args {@link CopyObjectArgs} object. + * @return {@link CompletableFuture}<{@link ObjectWriteResponse}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture copyObject(CopyObjectArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + args.validateSse(this.baseUrl); + + return CompletableFuture.supplyAsync( + () -> args.source().offset() != null && args.source().length() != null) + .thenCompose( + condition -> { + if (condition) { + try { + return statObjectAsync(new StatObjectArgs((ObjectReadArgs) args.source())); + } catch (InsufficientDataException + | InternalException + | InvalidKeyException + | IOException + | NoSuchAlgorithmException + | XmlParserException e) { + throw new CompletionException(e); + } + } + return CompletableFuture.completedFuture(null); + }) + .thenApply(stat -> (stat == null) ? (long) -1 : stat.size()) + .thenCompose( + size -> { + if (args.source().offset() != null + || args.source().length() != null + || size > ObjectWriteArgs.MAX_PART_SIZE) { + if (args.metadataDirective() != null + && args.metadataDirective() == Directive.COPY) { + throw new IllegalArgumentException( + "COPY metadata directive is not applicable to source object size greater than" + + " 5 GiB"); + } + if (args.taggingDirective() != null && args.taggingDirective() == Directive.COPY) { + throw new IllegalArgumentException( + "COPY tagging directive is not applicable to source object size greater than" + + " 5 GiB"); + } + + try { + return composeObject(new ComposeObjectArgs(args)); + } catch (InsufficientDataException + | InternalException + | InvalidKeyException + | IOException + | NoSuchAlgorithmException + | XmlParserException e) { + throw new CompletionException(e); + } + } + return CompletableFuture.completedFuture(null); + }) + .thenCompose( + objectWriteResponse -> { + if (objectWriteResponse != null) { + return CompletableFuture.completedFuture(objectWriteResponse); + } + + Multimap headers = args.genHeaders(); + + if (args.metadataDirective() != null) { + headers.put("x-amz-metadata-directive", args.metadataDirective().name()); + } + + if (args.taggingDirective() != null) { + headers.put("x-amz-tagging-directive", args.taggingDirective().name()); + } + + headers.putAll(args.source().genCopyHeaders()); + + try { + return executePutAsync(args, headers, null, null, 0) + .thenApply( + response -> { + try { + CopyObjectResult result = + Xml.unmarshal(CopyObjectResult.class, response.body().charStream()); + return new ObjectWriteResponse( + response.headers(), + args.bucket(), + args.region(), + args.object(), + result.etag(), + response.header("x-amz-version-id")); + } catch (XmlParserException e) { + throw new CompletionException(e); + } finally { + response.close(); + } + }); + } catch (InsufficientDataException + | InternalException + | InvalidKeyException + | IOException + | NoSuchAlgorithmException + | XmlParserException e) { + throw new CompletionException(e); + } + }); + } + + private CompletableFuture uploadPartCopy( + String bucketName, + String region, + String objectName, + String uploadId, + int partNumber, + Multimap headers, + Part[] parts) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + return uploadPartCopyAsync(bucketName, region, objectName, uploadId, partNumber, headers, null) + .thenApply( + uploadPartCopyResponse -> { + parts[partNumber - 1] = new Part(partNumber, uploadPartCopyResponse.result().etag()); + return parts; + }); + } + + /** + * Creates an object by combining data from different source objects using server-side copy. + * + *
Example:{@code
+   * List sourceObjectList = new ArrayList();
+   *
+   * sourceObjectList.add(
+   *    ComposeSource.builder().bucket("my-job-bucket").object("my-objectname-part-one").build());
+   * sourceObjectList.add(
+   *    ComposeSource.builder().bucket("my-job-bucket").object("my-objectname-part-two").build());
+   * sourceObjectList.add(
+   *    ComposeSource.builder().bucket("my-job-bucket").object("my-objectname-part-three").build());
+   *
+   * // Create my-bucketname/my-objectname by combining source object list.
+   * CompletableFuture future = minioAsyncClient.composeObject(
+   *    ComposeObjectArgs.builder()
+   *        .bucket("my-bucketname")
+   *        .object("my-objectname")
+   *        .sources(sourceObjectList)
+   *        .build());
+   *
+   * // Create my-bucketname/my-objectname with user metadata by combining source object
+   * // list.
+   * Map userMetadata = new HashMap<>();
+   * userMetadata.put("My-Project", "Project One");
+   * CompletableFuture future = minioAsyncClient.composeObject(
+   *     ComposeObjectArgs.builder()
+   *        .bucket("my-bucketname")
+   *        .object("my-objectname")
+   *        .sources(sourceObjectList)
+   *        .userMetadata(userMetadata)
+   *        .build());
+   *
+   * // Create my-bucketname/my-objectname with user metadata and server-side encryption
+   * // by combining source object list.
+   * CompletableFuture future = minioAsyncClient.composeObject(
+   *   ComposeObjectArgs.builder()
+   *        .bucket("my-bucketname")
+   *        .object("my-objectname")
+   *        .sources(sourceObjectList)
+   *        .userMetadata(userMetadata)
+   *        .ssec(sse)
+   *        .build());
+   * }
+ * + * @param args {@link ComposeObjectArgs} object. + * @return {@link CompletableFuture}<{@link ObjectWriteResponse}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture composeObject(ComposeObjectArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + args.validateSse(this.baseUrl); + List sources = args.sources(); + int[] partCount = {0}; + String[] uploadIdCopy = {null}; + + return calculatePartCountAsync(sources) + .thenApply( + count -> { + partCount[0] = count; + return (count == 1 + && args.sources().get(0).offset() == null + && args.sources().get(0).length() == null); + }) + .thenCompose( + copyObjectFlag -> { + if (copyObjectFlag) { + try { + return copyObject(new CopyObjectArgs(args)); + } catch (InsufficientDataException + | InternalException + | InvalidKeyException + | IOException + | NoSuchAlgorithmException + | XmlParserException e) { + throw new CompletionException(e); + } + } + return CompletableFuture.completedFuture(null); + }) + .thenCompose( + objectWriteResponse -> { + if (objectWriteResponse != null) { + return CompletableFuture.completedFuture(objectWriteResponse); + } + + CompletableFuture completableFuture = + CompletableFuture.supplyAsync( + () -> { + Multimap headers = newMultimap(args.extraHeaders()); + headers.putAll(args.genHeaders()); + return headers; + }) + .thenCompose( + headers -> { + try { + return createMultipartUploadAsync( + args.bucket(), + args.region(), + args.object(), + headers, + args.extraQueryParams()); + } catch (InsufficientDataException + | InternalException + | InvalidKeyException + | IOException + | NoSuchAlgorithmException + | XmlParserException e) { + throw new CompletionException(e); + } + }) + .thenApply( + createMultipartUploadResponse -> { + String uploadId = createMultipartUploadResponse.result().uploadId(); + uploadIdCopy[0] = uploadId; + return uploadId; + }) + .thenCompose( + uploadId -> { + Multimap ssecHeaders = HashMultimap.create(); + if (args.sse() != null + && args.sse() instanceof ServerSideEncryptionCustomerKey) { + ssecHeaders.putAll(newMultimap(args.sse().headers())); + } + + int partNumber = 0; + CompletableFuture future = + CompletableFuture.supplyAsync( + () -> { + return new Part[partCount[0]]; + }); + for (ComposeSource src : sources) { + long size = 0; + try { + size = src.objectSize(); + } catch (InternalException e) { + throw new CompletionException(e); + } + if (src.length() != null) { + size = src.length(); + } else if (src.offset() != null) { + size -= src.offset(); + } + long offset = 0; + if (src.offset() != null) offset = src.offset(); + + final Multimap headers; + try { + headers = newMultimap(src.headers()); + } catch (InternalException e) { + throw new CompletionException(e); + } + headers.putAll(ssecHeaders); + + if (size <= ObjectWriteArgs.MAX_PART_SIZE) { + partNumber++; + if (src.length() != null) { + headers.put( + "x-amz-copy-source-range", + "bytes=" + offset + "-" + (offset + src.length() - 1)); + } else if (src.offset() != null) { + headers.put( + "x-amz-copy-source-range", + "bytes=" + offset + "-" + (offset + size - 1)); + } + + final int partNum = partNumber; + future = + future.thenCompose( + parts -> { + try { + return uploadPartCopy( + args.bucket(), + args.region(), + args.object(), + uploadId, + partNum, + headers, + parts); + } catch (InsufficientDataException + | InternalException + | InvalidKeyException + | IOException + | NoSuchAlgorithmException + | XmlParserException e) { + throw new CompletionException(e); + } + }); + continue; + } + + while (size > 0) { + partNumber++; + + long length = size; + if (length > ObjectWriteArgs.MAX_PART_SIZE) { + length = ObjectWriteArgs.MAX_PART_SIZE; + } + long endBytes = offset + length - 1; + + Multimap headersCopy = newMultimap(headers); + headersCopy.put( + "x-amz-copy-source-range", "bytes=" + offset + "-" + endBytes); + + final int partNum = partNumber; + future = + future.thenCompose( + parts -> { + try { + return uploadPartCopy( + args.bucket(), + args.region(), + args.object(), + uploadId, + partNum, + headersCopy, + parts); + } catch (InsufficientDataException + | InternalException + | InvalidKeyException + | IOException + | NoSuchAlgorithmException + | XmlParserException e) { + throw new CompletionException(e); + } + }); + offset += length; + size -= length; + } + } + + return future; + }) + .thenCompose( + parts -> { + try { + return completeMultipartUploadAsync( + args.bucket(), + args.region(), + args.object(), + uploadIdCopy[0], + parts, + null, + null); + } catch (InsufficientDataException + | InternalException + | InvalidKeyException + | IOException + | NoSuchAlgorithmException + | XmlParserException e) { + throw new CompletionException(e); + } + }); + + completableFuture.exceptionally( + e -> { + if (uploadIdCopy[0] != null) { + try { + abortMultipartUploadAsync( + args.bucket(), + args.region(), + args.object(), + uploadIdCopy[0], + null, + null) + .get(); + } catch (InsufficientDataException + | InternalException + | InvalidKeyException + | IOException + | NoSuchAlgorithmException + | XmlParserException + | InterruptedException + | ExecutionException ex) { + throw new CompletionException(ex); + } + } + throw new CompletionException(e); + }); + return completableFuture; + }); + } + + /** + * Gets presigned URL of an object for HTTP method, expiry time and custom request parameters. + * + *
Example:{@code
+   * // Get presigned URL string to delete 'my-objectname' in 'my-bucketname' and its life time
+   * // is one day.
+   * String url =
+   *    minioAsyncClient.getPresignedObjectUrl(
+   *        GetPresignedObjectUrlArgs.builder()
+   *            .method(Method.DELETE)
+   *            .bucket("my-bucketname")
+   *            .object("my-objectname")
+   *            .expiry(24 * 60 * 60)
+   *            .build());
+   * System.out.println(url);
+   *
+   * // Get presigned URL string to upload 'my-objectname' in 'my-bucketname'
+   * // with response-content-type as application/json and life time as one day.
+   * Map reqParams = new HashMap();
+   * reqParams.put("response-content-type", "application/json");
+   *
+   * String url =
+   *    minioAsyncClient.getPresignedObjectUrl(
+   *        GetPresignedObjectUrlArgs.builder()
+   *            .method(Method.PUT)
+   *            .bucket("my-bucketname")
+   *            .object("my-objectname")
+   *            .expiry(1, TimeUnit.DAYS)
+   *            .extraQueryParams(reqParams)
+   *            .build());
+   * System.out.println(url);
+   *
+   * // Get presigned URL string to download 'my-objectname' in 'my-bucketname' and its life time
+   * // is 2 hours.
+   * String url =
+   *    minioAsyncClient.getPresignedObjectUrl(
+   *        GetPresignedObjectUrlArgs.builder()
+   *            .method(Method.GET)
+   *            .bucket("my-bucketname")
+   *            .object("my-objectname")
+   *            .expiry(2, TimeUnit.HOURS)
+   *            .build());
+   * System.out.println(url);
+   * }
+ * + * @param args {@link GetPresignedObjectUrlArgs} object. + * @return String - URL string. + * @throws ErrorResponseException thrown to indicate S3 service returned an error response. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws InvalidResponseException thrown to indicate S3 service returned invalid or no error + * response. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + * @throws ServerException + */ + public String getPresignedObjectUrl(GetPresignedObjectUrlArgs args) + throws ErrorResponseException, InsufficientDataException, InternalException, + InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException, + XmlParserException, ServerException { + checkArgs(args); + + byte[] body = + (args.method() == Method.PUT || args.method() == Method.POST) ? HttpUtils.EMPTY_BODY : null; + + Multimap queryParams = newMultimap(args.extraQueryParams()); + if (args.versionId() != null) queryParams.put("versionId", args.versionId()); + + String region = null; + try { + region = getRegionAsync(args.bucket(), args.region()).get(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + throwEncapsulatedException(e); + } + + if (provider == null) { + HttpUrl url = buildUrl(args.method(), args.bucket(), args.object(), region, queryParams); + return url.toString(); + } + + Credentials creds = provider.fetch(); + if (creds.sessionToken() != null) queryParams.put("X-Amz-Security-Token", creds.sessionToken()); + HttpUrl url = buildUrl(args.method(), args.bucket(), args.object(), region, queryParams); + Request request = + createRequest( + url, + args.method(), + args.extraHeaders() == null ? null : httpHeaders(args.extraHeaders()), + body, + 0, + creds); + url = Signer.presignV4(request, region, creds.accessKey(), creds.secretKey(), args.expiry()); + return url.toString(); + } + + /** + * Gets form-data of {@link PostPolicy} of an object to upload its data using POST method. + * + *
Example:{@code
+   * // Create new post policy for 'my-bucketname' with 7 days expiry from now.
+   * PostPolicy policy = new PostPolicy("my-bucketname", ZonedDateTime.now().plusDays(7));
+   *
+   * // Add condition that 'key' (object name) equals to 'my-objectname'.
+   * policy.addEqualsCondition("key", "my-objectname");
+   *
+   * // Add condition that 'Content-Type' starts with 'image/'.
+   * policy.addStartsWithCondition("Content-Type", "image/");
+   *
+   * // Add condition that 'content-length-range' is between 64kiB to 10MiB.
+   * policy.addContentLengthRangeCondition(64 * 1024, 10 * 1024 * 1024);
+   *
+   * Map formData = minioAsyncClient.getPresignedPostFormData(policy);
+   *
+   * // Upload an image using POST object with form-data.
+   * MultipartBody.Builder multipartBuilder = new MultipartBody.Builder();
+   * multipartBuilder.setType(MultipartBody.FORM);
+   * for (Map.Entry entry : formData.entrySet()) {
+   *   multipartBuilder.addFormDataPart(entry.getKey(), entry.getValue());
+   * }
+   * multipartBuilder.addFormDataPart("key", "my-objectname");
+   * multipartBuilder.addFormDataPart("Content-Type", "image/png");
+   *
+   * // "file" must be added at last.
+   * multipartBuilder.addFormDataPart(
+   *     "file", "my-objectname", RequestBody.create(new File("Pictures/avatar.png"), null));
+   *
+   * Request request =
+   *     new Request.Builder()
+   *         .url("https://play.min.io/my-bucketname")
+   *         .post(multipartBuilder.build())
+   *         .build();
+   * OkHttpClient httpClient = new OkHttpClient().newBuilder().build();
+   * Response response = httpClient.newCall(request).execute();
+   * if (response.isSuccessful()) {
+   *   System.out.println("Pictures/avatar.png is uploaded successfully using POST object");
+   * } else {
+   *   System.out.println("Failed to upload Pictures/avatar.png");
+   * }
+   * }
+ * + * @param policy Post policy of an object. + * @return {@code Map} - Contains form-data to upload an object using POST method. + * @throws ErrorResponseException thrown to indicate S3 service returned an error response. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws InvalidResponseException thrown to indicate S3 service returned invalid or no error + * response. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + * @see PostPolicy + */ + public Map getPresignedPostFormData(PostPolicy policy) + throws ErrorResponseException, InsufficientDataException, InternalException, + InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException, + ServerException, XmlParserException { + if (provider == null) { + throw new IllegalArgumentException( + "Anonymous access does not require presigned post form-data"); + } + + String region = null; + try { + region = getRegionAsync(policy.bucket(), null).get(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + throwEncapsulatedException(e); + } + return policy.formData(provider.fetch(), region); + } + + /** + * Removes an object. + * + *
Example:{@code
+   * // Remove object.
+   * CompletableFuture future = minioAsyncClient.removeObject(
+   *     RemoveObjectArgs.builder().bucket("my-bucketname").object("my-objectname").build());
+   *
+   * // Remove versioned object.
+   * CompletableFuture future = minioAsyncClient.removeObject(
+   *     RemoveObjectArgs.builder()
+   *         .bucket("my-bucketname")
+   *         .object("my-versioned-objectname")
+   *         .versionId("my-versionid")
+   *         .build());
+   *
+   * // Remove versioned object bypassing Governance mode.
+   * CompletableFuture future = minioAsyncClient.removeObject(
+   *     RemoveObjectArgs.builder()
+   *         .bucket("my-bucketname")
+   *         .object("my-versioned-objectname")
+   *         .versionId("my-versionid")
+   *         .bypassRetentionMode(true)
+   *         .build());
+   * }
+ * + * @param args {@link RemoveObjectArgs} object. + * @return {@link CompletableFuture}<{@link Void}> object. + * @throws ErrorResponseException thrown to indicate S3 service returned an error response. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws InvalidResponseException thrown to indicate S3 service returned invalid or no error + * response. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture removeObject(RemoveObjectArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executeDeleteAsync( + args, + args.bypassGovernanceMode() + ? newMultimap("x-amz-bypass-governance-retention", "true") + : null, + (args.versionId() != null) ? newMultimap("versionId", args.versionId()) : null) + .thenAccept(response -> response.close()); + } + + /** + * Removes multiple objects lazily. Its required to iterate the returned Iterable to perform + * removal. + * + *
Example:{@code
+   * List objects = new LinkedList<>();
+   * objects.add(new DeleteObject("my-objectname1"));
+   * objects.add(new DeleteObject("my-objectname2"));
+   * objects.add(new DeleteObject("my-objectname3"));
+   * Iterable> results =
+   *     minioAsyncClient.removeObjects(
+   *         RemoveObjectsArgs.builder().bucket("my-bucketname").objects(objects).build());
+   * for (Result result : results) {
+   *   DeleteError error = errorResult.get();
+   *   System.out.println(
+   *       "Error in deleting object " + error.objectName() + "; " + error.message());
+   * }
+   * }
+ * + * @param args {@link RemoveObjectsArgs} object. + * @return {@code Iterable>} - Lazy iterator contains object removal status. + */ + public Iterable> removeObjects(RemoveObjectsArgs args) { + checkArgs(args); + + return new Iterable>() { + @Override + public Iterator> iterator() { + return new Iterator>() { + private Result error = null; + private Iterator errorIterator = null; + private boolean completed = false; + private Iterator objectIter = args.objects().iterator(); + + private void setError() { + error = null; + while (errorIterator.hasNext()) { + DeleteError deleteError = errorIterator.next(); + if (!"NoSuchVersion".equals(deleteError.code())) { + error = new Result<>(deleteError); + break; + } + } + } + + private synchronized void populate() { + if (completed) { + return; + } + + try { + List objectList = new LinkedList<>(); + while (objectIter.hasNext() && objectList.size() < 1000) { + objectList.add(objectIter.next()); + } + + completed = objectList.isEmpty(); + if (completed) return; + DeleteObjectsResponse response = null; + try { + response = + deleteObjectsAsync( + args.bucket(), + args.region(), + objectList, + true, + args.bypassGovernanceMode(), + args.extraHeaders(), + args.extraQueryParams()) + .get(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + throwEncapsulatedException(e); + } + if (!response.result().errorList().isEmpty()) { + errorIterator = response.result().errorList().iterator(); + setError(); + completed = true; + } + } catch (ErrorResponseException + | InsufficientDataException + | InternalException + | InvalidKeyException + | InvalidResponseException + | IOException + | NoSuchAlgorithmException + | ServerException + | XmlParserException e) { + error = new Result<>(e); + completed = true; + } + } + + @Override + public boolean hasNext() { + while (error == null && errorIterator == null && !completed) { + populate(); + } + + if (error == null && errorIterator != null) setError(); + if (error != null) return true; + if (completed) return false; + + errorIterator = null; + return hasNext(); + } + + @Override + public Result next() { + if (!hasNext()) throw new NoSuchElementException(); + + if (this.error != null) { + Result error = this.error; + this.error = null; + return error; + } + + // This never happens. + throw new NoSuchElementException(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + }; + } + + /** + * Restores an object asynchronously. + * + *
Example:{@code
+   * // Restore object.
+   * CompletableFuture future = minioAsyncClient.restoreObject(
+   *     RestoreObjectArgs.builder()
+   *         .bucket("my-bucketname")
+   *         .object("my-objectname")
+   *         .request(new RestoreRequest(null, null, null, null, null, null))
+   *         .build());
+   *
+   * // Restore versioned object.
+   * CompletableFuture future = minioAsyncClient.restoreObject(
+   *     RestoreObjectArgs.builder()
+   *         .bucket("my-bucketname")
+   *         .object("my-versioned-objectname")
+   *         .versionId("my-versionid")
+   *         .request(new RestoreRequest(null, null, null, null, null, null))
+   *         .build());
+   * }
+ * + * @param args {@link RestoreObjectArgs} object. + * @return {@link CompletableFuture}<{@link Void}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture restoreObject(RestoreObjectArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executePostAsync(args, null, newMultimap("restore", ""), args.request()) + .thenAccept(response -> response.close()); + } + + /** + * Lists objects information optionally with versions of a bucket. Supports both the versions 1 + * and 2 of the S3 API. By default, the version 2 API + * is used.
+ * Version 1 + * can be used by passing the optional argument {@code useVersion1} as {@code true}. + * + *
Example:{@code
+   * // Lists objects information.
+   * Iterable> results = minioAsyncClient.listObjects(
+   *     ListObjectsArgs.builder().bucket("my-bucketname").build());
+   *
+   * // Lists objects information recursively.
+   * Iterable> results = minioAsyncClient.listObjects(
+   *     ListObjectsArgs.builder().bucket("my-bucketname").recursive(true).build());
+   *
+   * // Lists maximum 100 objects information whose names starts with 'E' and after
+   * // 'ExampleGuide.pdf'.
+   * Iterable> results = minioAsyncClient.listObjects(
+   *     ListObjectsArgs.builder()
+   *         .bucket("my-bucketname")
+   *         .startAfter("ExampleGuide.pdf")
+   *         .prefix("E")
+   *         .maxKeys(100)
+   *         .build());
+   *
+   * // Lists maximum 100 objects information with version whose names starts with 'E' and after
+   * // 'ExampleGuide.pdf'.
+   * Iterable> results = minioAsyncClient.listObjects(
+   *     ListObjectsArgs.builder()
+   *         .bucket("my-bucketname")
+   *         .startAfter("ExampleGuide.pdf")
+   *         .prefix("E")
+   *         .maxKeys(100)
+   *         .includeVersions(true)
+   *         .build());
+   * }
+ * + * @param args Instance of {@link ListObjectsArgs} built using the builder + * @return {@code Iterable>} - Lazy iterator contains object information. + * @throws XmlParserException upon parsing response xml + */ + public Iterable> listObjects(ListObjectsArgs args) { + if (args.includeVersions() || args.versionIdMarker() != null) { + return listObjectVersions(args); + } + + if (args.useApiVersion1()) { + return listObjectsV1(args); + } + + return listObjectsV2(args); + } + + /** + * Lists bucket information of all buckets. + * + *
Example:{@code
+   * CompletableFuture> future = minioAsyncClient.listBuckets();
+   * }
+ * + * @return {@link CompletableFuture}<{@link List}<{@link Bucket}>> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture> listBuckets() + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + return listBuckets(ListBucketsArgs.builder().build()); + } + + /** + * Lists bucket information of all buckets. + * + *
Example:{@code
+   * CompletableFuture> future =
+   *     minioAsyncClient.listBuckets(ListBucketsArgs.builder().extraHeaders(headers).build());
+   * }
+ * + * @return {@link CompletableFuture}<{@link List}<{@link Bucket}>> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture> listBuckets(ListBucketsArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + return executeGetAsync(args, null, null) + .thenApply( + response -> { + try { + ListAllMyBucketsResult result = + Xml.unmarshal(ListAllMyBucketsResult.class, response.body().charStream()); + return result.buckets(); + } catch (XmlParserException e) { + throw new CompletionException(e); + } finally { + response.close(); + } + }); + } + + /** + * Checks if a bucket exists. + * + *
Example:{@code
+   * CompletableFuture future =
+   *      minioAsyncClient.bucketExists(BucketExistsArgs.builder().bucket("my-bucketname").build());
+   * }
+ * + * @param args {@link BucketExistsArgs} object. + * @return {@link CompletableFuture}<{@link Boolean}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture bucketExists(BucketExistsArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + return executeHeadAsync(args, null, null) + .exceptionally( + e -> { + Throwable ex = e.getCause(); + + if (ex instanceof CompletionException) { + ex = ((CompletionException) ex).getCause(); + } + + if (ex instanceof ExecutionException) { + ex = ((ExecutionException) ex).getCause(); + } + + if (ex instanceof ErrorResponseException) { + if (((ErrorResponseException) ex).errorResponse().code().equals(NO_SUCH_BUCKET)) { + return null; + } + } + throw new CompletionException(ex); + }) + .thenApply( + response -> { + try { + return response != null; + } finally { + if (response != null) response.close(); + } + }); + } + + /** + * Creates a bucket with region and object lock. + * + *
Example:{@code
+   * // Create bucket with default region.
+   * CompletableFuture future = minioAsyncClient.makeBucket(
+   *     MakeBucketArgs.builder()
+   *         .bucket("my-bucketname")
+   *         .build());
+   *
+   * // Create bucket with specific region.
+   * CompletableFuture future = minioAsyncClient.makeBucket(
+   *     MakeBucketArgs.builder()
+   *         .bucket("my-bucketname")
+   *         .region("us-west-1")
+   *         .build());
+   *
+   * // Create object-lock enabled bucket with specific region.
+   * CompletableFuture future = minioAsyncClient.makeBucket(
+   *     MakeBucketArgs.builder()
+   *         .bucket("my-bucketname")
+   *         .region("us-west-1")
+   *         .objectLock(true)
+   *         .build());
+   * }
+ * + * @param args Object with bucket name, region and lock functionality + * @return {@link CompletableFuture}<{@link Void}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture makeBucket(MakeBucketArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + + String region = args.region(); + if (this.region != null && !this.region.isEmpty()) { + // Error out if region does not match with region passed via constructor. + if (region != null && !region.equals(this.region)) { + throw new IllegalArgumentException( + "region must be " + this.region + ", but passed " + region); + } + + region = this.region; + } + + if (region == null) { + region = US_EAST_1; + } + + Multimap headers = + args.objectLock() ? newMultimap("x-amz-bucket-object-lock-enabled", "true") : null; + final String location = region; + + return executeAsync( + Method.PUT, + args.bucket(), + null, + location, + httpHeaders(merge(args.extraHeaders(), headers)), + args.extraQueryParams(), + location.equals(US_EAST_1) ? null : new CreateBucketConfiguration(location), + 0) + .thenAccept( + response -> { + regionCache.put(args.bucket(), location); + response.close(); + }); + } + + /** + * Sets versioning configuration of a bucket. + * + *
Example:{@code
+   * CompletableFuture future = minioAsyncClient.setBucketVersioning(
+   *     SetBucketVersioningArgs.builder().bucket("my-bucketname").config(config).build());
+   * }
+ * + * @param args {@link SetBucketVersioningArgs} object. + * @return {@link CompletableFuture}<{@link Void}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture setBucketVersioning(SetBucketVersioningArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executePutAsync(args, null, newMultimap("versioning", ""), args.config(), 0) + .thenAccept(response -> response.close()); + } + + /** + * Gets versioning configuration of a bucket. + * + *
Example:{@code
+   * CompletableFuture future =
+   *     minioAsyncClient.getBucketVersioning(
+   *         GetBucketVersioningArgs.builder().bucket("my-bucketname").build());
+   * }
+ * + * @param args {@link GetBucketVersioningArgs} object. + * @return {@link CompletableFuture}<{@link VersioningConfiguration}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture getBucketVersioning( + GetBucketVersioningArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executeGetAsync(args, null, newMultimap("versioning", "")) + .thenApply( + response -> { + try { + return Xml.unmarshal(VersioningConfiguration.class, response.body().charStream()); + } catch (XmlParserException e) { + throw new CompletionException(e); + } finally { + response.close(); + } + }); + } + + /** + * Sets default object retention in a bucket. + * + *
Example:{@code
+   * ObjectLockConfiguration config = new ObjectLockConfiguration(
+   *     RetentionMode.COMPLIANCE, new RetentionDurationDays(100));
+   * CompletableFuture future = minioAsyncClient.setObjectLockConfiguration(
+   *     SetObjectLockConfigurationArgs.builder().bucket("my-bucketname").config(config).build());
+   * }
+ * + * @param args {@link SetObjectLockConfigurationArgs} object. + * @return {@link CompletableFuture}<{@link Void}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture setObjectLockConfiguration(SetObjectLockConfigurationArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executePutAsync(args, null, newMultimap("object-lock", ""), args.config(), 0) + .thenAccept(response -> response.close()); + } + + /** + * Deletes default object retention in a bucket. + * + *
Example:{@code
+   * CompletableFuture future = minioAsyncClient.deleteObjectLockConfiguration(
+   *     DeleteObjectLockConfigurationArgs.builder().bucket("my-bucketname").build());
+   * }
+ * + * @param args {@link DeleteObjectLockConfigurationArgs} object. + * @return {@link CompletableFuture}<{@link Void}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture deleteObjectLockConfiguration( + DeleteObjectLockConfigurationArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executePutAsync( + args, null, newMultimap("object-lock", ""), new ObjectLockConfiguration(), 0) + .thenAccept(response -> response.close()); + } + + /** + * Gets default object retention in a bucket. + * + *
Example:{@code
+   * CompletableFuture future =
+   *     minioAsyncClient.getObjectLockConfiguration(
+   *         GetObjectLockConfigurationArgs.builder().bucket("my-bucketname").build());
+   * }
+ * + * @param args {@link GetObjectLockConfigurationArgs} object. + * @return {@link CompletableFuture}<{@link ObjectLockConfiguration}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture getObjectLockConfiguration( + GetObjectLockConfigurationArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executeGetAsync(args, null, newMultimap("object-lock", "")) + .thenApply( + response -> { + try { + return Xml.unmarshal(ObjectLockConfiguration.class, response.body().charStream()); + } catch (XmlParserException e) { + throw new CompletionException(e); + } finally { + response.close(); + } + }); + } + + /** + * Sets retention configuration to an object. + * + *
Example:{@code
+   *  Retention retention = new Retention(
+   *       RetentionMode.COMPLIANCE, ZonedDateTime.now().plusYears(1));
+   *  CompletableFuture future = minioAsyncClient.setObjectRetention(
+   *      SetObjectRetentionArgs.builder()
+   *          .bucket("my-bucketname")
+   *          .object("my-objectname")
+   *          .config(config)
+   *          .bypassGovernanceMode(true)
+   *          .build());
+   * }
+ * + * @param args {@link SetObjectRetentionArgs} object. + * @return {@link CompletableFuture}<{@link Void}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture setObjectRetention(SetObjectRetentionArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + Multimap queryParams = newMultimap("retention", ""); + if (args.versionId() != null) queryParams.put("versionId", args.versionId()); + return executePutAsync( + args, + args.bypassGovernanceMode() + ? newMultimap("x-amz-bypass-governance-retention", "True") + : null, + queryParams, + args.config(), + 0) + .thenAccept(response -> response.close()); + } + + /** + * Gets retention configuration of an object. + * + *
Example:{@code
+   * CompletableFuture future =
+   *     minioAsyncClient.getObjectRetention(GetObjectRetentionArgs.builder()
+   *        .bucket(bucketName)
+   *        .object(objectName)
+   *        .versionId(versionId)
+   *        .build());
+   * }
+ * + * @param args {@link GetObjectRetentionArgs} object. + * @return {@link CompletableFuture}<{@link Retention}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture getObjectRetention(GetObjectRetentionArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + Multimap queryParams = newMultimap("retention", ""); + if (args.versionId() != null) queryParams.put("versionId", args.versionId()); + return executeGetAsync(args, null, queryParams) + .exceptionally( + e -> { + Throwable ex = e.getCause(); + + if (ex instanceof CompletionException) { + ex = ((CompletionException) ex).getCause(); + } + + if (ex instanceof ExecutionException) { + ex = ((ExecutionException) ex).getCause(); + } + + if (ex instanceof ErrorResponseException) { + if (((ErrorResponseException) ex) + .errorResponse() + .code() + .equals(NO_SUCH_OBJECT_LOCK_CONFIGURATION)) { + return null; + } + } + throw new CompletionException(ex); + }) + .thenApply( + response -> { + if (response == null) return null; + try { + return Xml.unmarshal(Retention.class, response.body().charStream()); + } catch (XmlParserException e) { + throw new CompletionException(e); + } finally { + response.close(); + } + }); + } + + /** + * Enables legal hold on an object. + * + *
Example:{@code
+   * CompletableFuture future = minioAsyncClient.enableObjectLegalHold(
+   *    EnableObjectLegalHoldArgs.builder()
+   *        .bucket("my-bucketname")
+   *        .object("my-objectname")
+   *        .versionId("object-versionId")
+   *        .build());
+   * }
+ * + * @param args {@link EnableObjectLegalHoldArgs} object. + * @return {@link CompletableFuture}<{@link Void}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture enableObjectLegalHold(EnableObjectLegalHoldArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + Multimap queryParams = newMultimap("legal-hold", ""); + if (args.versionId() != null) queryParams.put("versionId", args.versionId()); + return executePutAsync(args, null, queryParams, new LegalHold(true), 0) + .thenAccept(response -> response.close()); + } + + /** + * Disables legal hold on an object. + * + *
Example:{@code
+   * CompletableFuture future = minioAsyncClient.disableObjectLegalHold(
+   *    DisableObjectLegalHoldArgs.builder()
+   *        .bucket("my-bucketname")
+   *        .object("my-objectname")
+   *        .versionId("object-versionId")
+   *        .build());
+   * }
+ * + * @param args {@link DisableObjectLegalHoldArgs} object. + * @return {@link CompletableFuture}<{@link Void}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture disableObjectLegalHold(DisableObjectLegalHoldArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + Multimap queryParams = newMultimap("legal-hold", ""); + if (args.versionId() != null) queryParams.put("versionId", args.versionId()); + return executePutAsync(args, null, queryParams, new LegalHold(false), 0) + .thenAccept(response -> response.close()); + } + + /** + * Returns true if legal hold is enabled on an object. + * + *
Example:{@code
+   * CompletableFuture future =
+   *     s3Client.isObjectLegalHoldEnabled(
+   *        IsObjectLegalHoldEnabledArgs.builder()
+   *             .bucket("my-bucketname")
+   *             .object("my-objectname")
+   *             .versionId("object-versionId")
+   *             .build());
+   * }
+ * + * @param args {@link IsObjectLegalHoldEnabledArgs} object. + * @return {@link CompletableFuture}<{@link Boolean}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture isObjectLegalHoldEnabled(IsObjectLegalHoldEnabledArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + Multimap queryParams = newMultimap("legal-hold", ""); + if (args.versionId() != null) queryParams.put("versionId", args.versionId()); + return executeGetAsync(args, null, queryParams) + .exceptionally( + e -> { + Throwable ex = e.getCause(); + + if (ex instanceof CompletionException) { + ex = ((CompletionException) ex).getCause(); + } + + if (ex instanceof ExecutionException) { + ex = ((ExecutionException) ex).getCause(); + } + + if (ex instanceof ErrorResponseException) { + if (((ErrorResponseException) ex) + .errorResponse() + .code() + .equals(NO_SUCH_OBJECT_LOCK_CONFIGURATION)) { + return null; + } + } + throw new CompletionException(ex); + }) + .thenApply( + response -> { + if (response == null) return false; + try { + LegalHold result = Xml.unmarshal(LegalHold.class, response.body().charStream()); + return result.status(); + } catch (XmlParserException e) { + throw new CompletionException(e); + } finally { + response.close(); + } + }); + } + + /** + * Removes an empty bucket using arguments + * + *
Example:{@code
+   * CompletableFuture future =
+   *     minioAsyncClient.removeBucket(RemoveBucketArgs.builder().bucket("my-bucketname").build());
+   * }
+ * + * @param args {@link RemoveBucketArgs} bucket. + * @return {@link CompletableFuture}<{@link Void}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture removeBucket(RemoveBucketArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executeDeleteAsync(args, null, null) + .thenAccept(response -> regionCache.remove(args.bucket())); + } + + /** + * Uploads data from a stream to an object. + * + *
Example:{@code
+   * // Upload known sized input stream.
+   * CompletableFuture future = minioAsyncClient.putObject(
+   *     PutObjectArgs.builder().bucket("my-bucketname").object("my-objectname").stream(
+   *             inputStream, size, -1)
+   *         .contentType("video/mp4")
+   *         .build());
+   *
+   * // Upload unknown sized input stream.
+   * CompletableFuture future = minioAsyncClient.putObject(
+   *     PutObjectArgs.builder().bucket("my-bucketname").object("my-objectname").stream(
+   *             inputStream, -1, 10485760)
+   *         .contentType("video/mp4")
+   *         .build());
+   *
+   * // Create object ends with '/' (also called as folder or directory).
+   * CompletableFuture future = minioAsyncClient.putObject(
+   *     PutObjectArgs.builder().bucket("my-bucketname").object("path/to/").stream(
+   *             new ByteArrayInputStream(new byte[] {}), 0, -1)
+   *         .build());
+   *
+   * // Upload input stream with headers and user metadata.
+   * Map headers = new HashMap<>();
+   * headers.put("X-Amz-Storage-Class", "REDUCED_REDUNDANCY");
+   * Map userMetadata = new HashMap<>();
+   * userMetadata.put("My-Project", "Project One");
+   * CompletableFuture future = minioAsyncClient.putObject(
+   *     PutObjectArgs.builder().bucket("my-bucketname").object("my-objectname").stream(
+   *             inputStream, size, -1)
+   *         .headers(headers)
+   *         .userMetadata(userMetadata)
+   *         .build());
+   *
+   * // Upload input stream with server-side encryption.
+   * CompletableFuture future = minioAsyncClient.putObject(
+   *     PutObjectArgs.builder().bucket("my-bucketname").object("my-objectname").stream(
+   *             inputStream, size, -1)
+   *         .sse(sse)
+   *         .build());
+   * }
+ * + * @param args {@link PutObjectArgs} object. + * @return {@link CompletableFuture}<{@link ObjectWriteResponse}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture putObject(PutObjectArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + args.validateSse(this.baseUrl); + return putObjectAsync( + args, + args.stream(), + args.objectSize(), + args.partSize(), + args.partCount(), + args.contentType()); + } + + /** + * Uploads data from a file to an object. + * + *
Example:{@code
+   * // Upload an JSON file.
+   * CompletableFuture future = minioAsyncClient.uploadObject(
+   *     UploadObjectArgs.builder()
+   *         .bucket("my-bucketname").object("my-objectname").filename("person.json").build());
+   *
+   * // Upload a video file.
+   * CompletableFuture future = minioAsyncClient.uploadObject(
+   *     UploadObjectArgs.builder()
+   *         .bucket("my-bucketname")
+   *         .object("my-objectname")
+   *         .filename("my-video.avi")
+   *         .contentType("video/mp4")
+   *         .build());
+   * }
+ * + * @param args {@link UploadObjectArgs} object. + * @return {@link CompletableFuture}<{@link ObjectWriteResponse}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture uploadObject(UploadObjectArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + args.validateSse(this.baseUrl); + final RandomAccessFile file = new RandomAccessFile(args.filename(), "r"); + return putObjectAsync( + args, file, args.objectSize(), args.partSize(), args.partCount(), args.contentType()) + .exceptionally( + e -> { + try { + file.close(); + } catch (IOException ex) { + throw new CompletionException(ex); + } + + Throwable ex = e.getCause(); + + if (ex instanceof CompletionException) { + ex = ((CompletionException) ex).getCause(); + } + + if (ex instanceof ExecutionException) { + ex = ((ExecutionException) ex).getCause(); + } + + throw new CompletionException(ex); + }) + .thenApply( + objectWriteResponse -> { + try { + file.close(); + } catch (IOException e) { + throw new CompletionException(e); + } + return objectWriteResponse; + }); + } + + /** + * Gets bucket policy configuration of a bucket. + * + *
Example:{@code
+   * CompletableFuture future =
+   *     minioAsyncClient.getBucketPolicy(
+   *         GetBucketPolicyArgs.builder().bucket("my-bucketname").build());
+   * }
+ * + * @param args {@link GetBucketPolicyArgs} object. + * @return {@link CompletableFuture}<{@link String}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture getBucketPolicy(GetBucketPolicyArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executeGetAsync(args, null, newMultimap("policy", "")) + .exceptionally( + e -> { + Throwable ex = e.getCause(); + + if (ex instanceof CompletionException) { + ex = ((CompletionException) ex).getCause(); + } + + if (ex instanceof ExecutionException) { + ex = ((ExecutionException) ex).getCause(); + } + + if (ex instanceof ErrorResponseException) { + if (((ErrorResponseException) ex) + .errorResponse() + .code() + .equals(NO_SUCH_BUCKET_POLICY)) { + return null; + } + } + throw new CompletionException(ex); + }) + .thenApply( + response -> { + if (response == null) return ""; + try { + byte[] buf = new byte[MAX_BUCKET_POLICY_SIZE]; + int bytesRead = 0; + bytesRead = response.body().byteStream().read(buf, 0, MAX_BUCKET_POLICY_SIZE); + if (bytesRead < 0) { + throw new CompletionException( + new IOException("unexpected EOF when reading bucket policy")); + } + + // Read one byte extra to ensure only MAX_BUCKET_POLICY_SIZE data is sent by the + // server. + if (bytesRead == MAX_BUCKET_POLICY_SIZE) { + int byteRead = 0; + while (byteRead == 0) { + byteRead = response.body().byteStream().read(); + if (byteRead < 0) { + break; // reached EOF which is fine. + } + + if (byteRead > 0) { + throw new CompletionException( + new BucketPolicyTooLargeException(args.bucket())); + } + } + } + + return new String(buf, 0, bytesRead, StandardCharsets.UTF_8); + } catch (IOException e) { + throw new CompletionException(e); + } finally { + response.close(); + } + }); + } + + /** + * Sets bucket policy configuration to a bucket. + * + *
Example:{@code
+   * // Assume policyJson contains below JSON string;
+   * // {
+   * //     "Statement": [
+   * //         {
+   * //             "Action": [
+   * //                 "s3:GetBucketLocation",
+   * //                 "s3:ListBucket"
+   * //             ],
+   * //             "Effect": "Allow",
+   * //             "Principal": "*",
+   * //             "Resource": "arn:aws:s3:::my-bucketname"
+   * //         },
+   * //         {
+   * //             "Action": "s3:GetObject",
+   * //             "Effect": "Allow",
+   * //             "Principal": "*",
+   * //             "Resource": "arn:aws:s3:::my-bucketname/myobject*"
+   * //         }
+   * //     ],
+   * //     "Version": "2012-10-17"
+   * // }
+   * //
+   * CompletableFuture future = minioAsyncClient.setBucketPolicy(
+   *     SetBucketPolicyArgs.builder().bucket("my-bucketname").config(policyJson).build());
+   * }
+ * + * @param args {@link SetBucketPolicyArgs} object. + * @return {@link CompletableFuture}<{@link Void}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture setBucketPolicy(SetBucketPolicyArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executePutAsync( + args, + newMultimap("Content-Type", "application/json"), + newMultimap("policy", ""), + args.config(), + 0) + .thenAccept(response -> response.close()); + } + + /** + * Deletes bucket policy configuration to a bucket. + * + *
Example:{@code
+   * CompletableFuture future =
+   *     minioAsyncClient.deleteBucketPolicy(
+   *         DeleteBucketPolicyArgs.builder().bucket("my-bucketname"));
+   * }
+ * + * @param args {@link DeleteBucketPolicyArgs} object. + * @return {@link CompletableFuture}<{@link Void}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture deleteBucketPolicy(DeleteBucketPolicyArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executeDeleteAsync(args, null, newMultimap("policy", "")) + .exceptionally( + e -> { + Throwable ex = e.getCause(); + + if (ex instanceof CompletionException) { + ex = ((CompletionException) ex).getCause(); + } + + if (ex instanceof ExecutionException) { + ex = ((ExecutionException) ex).getCause(); + } + + if (ex instanceof ErrorResponseException) { + if (((ErrorResponseException) ex) + .errorResponse() + .code() + .equals(NO_SUCH_BUCKET_POLICY)) { + return null; + } + } + throw new CompletionException(ex); + }) + .thenAccept( + response -> { + if (response != null) response.close(); + }); + } + + /** + * Sets lifecycle configuration to a bucket. + * + *
Example:{@code
+   * List rules = new LinkedList<>();
+   * rules.add(
+   *     new LifecycleRule(
+   *         Status.ENABLED,
+   *         null,
+   *         new Expiration((ZonedDateTime) null, 365, null),
+   *         new RuleFilter("logs/"),
+   *         "rule2",
+   *         null,
+   *         null,
+   *         null));
+   * LifecycleConfiguration config = new LifecycleConfiguration(rules);
+   * CompletableFuture future = minioAsyncClient.setBucketLifecycle(
+   *     SetBucketLifecycleArgs.builder().bucket("my-bucketname").config(config).build());
+   * }
+ * + * @param args {@link SetBucketLifecycleArgs} object. + * @return {@link CompletableFuture}<{@link Void}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture setBucketLifecycle(SetBucketLifecycleArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executePutAsync(args, null, newMultimap("lifecycle", ""), args.config(), 0) + .thenAccept(response -> response.close()); + } + + /** + * Deletes lifecycle configuration of a bucket. + * + *
Example:{@code
+   * CompletableFuture future = deleteBucketLifecycle(
+   *     DeleteBucketLifecycleArgs.builder().bucket("my-bucketname").build());
+   * }
+ * + * @param args {@link DeleteBucketLifecycleArgs} object. + * @return {@link CompletableFuture}<{@link Void}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture deleteBucketLifecycle(DeleteBucketLifecycleArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executeDeleteAsync(args, null, newMultimap("lifecycle", "")) + .thenAccept(response -> response.close()); + } + + /** + * Gets lifecycle configuration of a bucket. + * + *
Example:{@code
+   * CompletableFuture future =
+   *     minioAsyncClient.getBucketLifecycle(
+   *         GetBucketLifecycleArgs.builder().bucket("my-bucketname").build());
+   * }
+ * + * @param args {@link GetBucketLifecycleArgs} object. + * @return {@link LifecycleConfiguration} object. + * @return {@link CompletableFuture}<{@link LifecycleConfiguration}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture getBucketLifecycle(GetBucketLifecycleArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executeGetAsync(args, null, newMultimap("lifecycle", "")) + .exceptionally( + e -> { + Throwable ex = e.getCause(); + + if (ex instanceof CompletionException) { + ex = ((CompletionException) ex).getCause(); + } + + if (ex instanceof ExecutionException) { + ex = ((ExecutionException) ex).getCause(); + } + + if (ex instanceof ErrorResponseException) { + if (((ErrorResponseException) ex) + .errorResponse() + .code() + .equals("NoSuchLifecycleConfiguration")) { + return null; + } + } + throw new CompletionException(ex); + }) + .thenApply( + response -> { + if (response == null) return null; + try { + return Xml.unmarshal(LifecycleConfiguration.class, response.body().charStream()); + } catch (XmlParserException e) { + throw new CompletionException(e); + } finally { + response.close(); + } + }); + } + + /** + * Gets notification configuration of a bucket. + * + *
Example:{@code
+   * CompletableFuture future =
+   *     minioAsyncClient.getBucketNotification(
+   *         GetBucketNotificationArgs.builder().bucket("my-bucketname").build());
+   * }
+ * + * @param args {@link GetBucketNotificationArgs} object. + * @return {@link CompletableFuture}<{@link NotificationConfiguration}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture getBucketNotification( + GetBucketNotificationArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executeGetAsync(args, null, newMultimap("notification", "")) + .thenApply( + response -> { + try { + return Xml.unmarshal(NotificationConfiguration.class, response.body().charStream()); + } catch (XmlParserException e) { + throw new CompletionException(e); + } finally { + response.close(); + } + }); + } + + /** + * Sets notification configuration to a bucket. + * + *
Example:{@code
+   * List eventList = new LinkedList<>();
+   * eventList.add(EventType.OBJECT_CREATED_PUT);
+   * eventList.add(EventType.OBJECT_CREATED_COPY);
+   *
+   * QueueConfiguration queueConfiguration = new QueueConfiguration();
+   * queueConfiguration.setQueue("arn:minio:sqs::1:webhook");
+   * queueConfiguration.setEvents(eventList);
+   * queueConfiguration.setPrefixRule("images");
+   * queueConfiguration.setSuffixRule("pg");
+   *
+   * List queueConfigurationList = new LinkedList<>();
+   * queueConfigurationList.add(queueConfiguration);
+   *
+   * NotificationConfiguration config = new NotificationConfiguration();
+   * config.setQueueConfigurationList(queueConfigurationList);
+   *
+   * CompletableFuture future = minioAsyncClient.setBucketNotification(
+   *     SetBucketNotificationArgs.builder().bucket("my-bucketname").config(config).build());
+   * }
+ * + * @param args {@link SetBucketNotificationArgs} object. + * @return {@link CompletableFuture}<{@link Void}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture setBucketNotification(SetBucketNotificationArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executePutAsync(args, null, newMultimap("notification", ""), args.config(), 0) + .thenAccept(response -> response.close()); + } + + /** + * Deletes notification configuration of a bucket. + * + *
Example:{@code
+   * CompletableFuture future = minioAsyncClient.deleteBucketNotification(
+   *     DeleteBucketNotificationArgs.builder().bucket("my-bucketname").build());
+   * }
+ * + * @param args {@link DeleteBucketNotificationArgs} object. + * @return {@link CompletableFuture}<{@link Void}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture deleteBucketNotification(DeleteBucketNotificationArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executePutAsync( + args, null, newMultimap("notification", ""), new NotificationConfiguration(), 0) + .thenAccept(response -> response.close()); + } + + /** + * Gets bucket replication configuration of a bucket. + * + *
Example:{@code
+   * CompletableFuture future =
+   *     minioAsyncClient.getBucketReplication(
+   *         GetBucketReplicationArgs.builder().bucket("my-bucketname").build());
+   * }
+ * + * @param args {@link GetBucketReplicationArgs} object. + * @return {@link CompletableFuture}<{@link ReplicationConfiguration}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture getBucketReplication( + GetBucketReplicationArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executeGetAsync(args, null, newMultimap("replication", "")) + .exceptionally( + e -> { + Throwable ex = e.getCause(); + + if (ex instanceof CompletionException) { + ex = ((CompletionException) ex).getCause(); + } + + if (ex instanceof ExecutionException) { + ex = ((ExecutionException) ex).getCause(); + } + + if (ex instanceof ErrorResponseException) { + if (((ErrorResponseException) ex) + .errorResponse() + .code() + .equals("ReplicationConfigurationNotFoundError")) { + return null; + } + } + throw new CompletionException(ex); + }) + .thenApply( + response -> { + if (response == null) return null; + try { + return Xml.unmarshal(ReplicationConfiguration.class, response.body().charStream()); + } catch (XmlParserException e) { + throw new CompletionException(e); + } finally { + response.close(); + } + }); + } + + /** + * Sets bucket replication configuration to a bucket. + * + *
Example:{@code
+   * Map tags = new HashMap<>();
+   * tags.put("key1", "value1");
+   * tags.put("key2", "value2");
+   *
+   * ReplicationRule rule =
+   *     new ReplicationRule(
+   *         new DeleteMarkerReplication(Status.DISABLED),
+   *         new ReplicationDestination(
+   *             null, null, "REPLACE-WITH-ACTUAL-DESTINATION-BUCKET-ARN", null, null, null, null),
+   *         null,
+   *         new RuleFilter(new AndOperator("TaxDocs", tags)),
+   *         "rule1",
+   *         null,
+   *         1,
+   *         null,
+   *         Status.ENABLED);
+   *
+   * List rules = new LinkedList<>();
+   * rules.add(rule);
+   *
+   * ReplicationConfiguration config =
+   *     new ReplicationConfiguration("REPLACE-WITH-ACTUAL-ROLE", rules);
+   *
+   * CompletableFuture future = minioAsyncClient.setBucketReplication(
+   *     SetBucketReplicationArgs.builder().bucket("my-bucketname").config(config).build());
+   * }
+ * + * @param args {@link SetBucketReplicationArgs} object. + * @return {@link CompletableFuture}<{@link Void}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture setBucketReplication(SetBucketReplicationArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executePutAsync( + args, + (args.objectLockToken() != null) + ? newMultimap("x-amz-bucket-object-lock-token", args.objectLockToken()) + : null, + newMultimap("replication", ""), + args.config(), + 0) + .thenAccept(response -> response.close()); + } + + /** + * Deletes bucket replication configuration from a bucket. + * + *
Example:{@code
+   * CompletableFuture future = minioAsyncClient.deleteBucketReplication(
+   *     DeleteBucketReplicationArgs.builder().bucket("my-bucketname"));
+   * }
+ * + * @param args {@link DeleteBucketReplicationArgs} object. + * @return {@link CompletableFuture}<{@link Void}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture deleteBucketReplication(DeleteBucketReplicationArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executeDeleteAsync(args, null, newMultimap("replication", "")) + .thenAccept(response -> response.close()); + } + + /** + * Listens events of object prefix and suffix of a bucket. The returned closable iterator is + * lazily evaluated hence its required to iterate to get new records and must be used with + * try-with-resource to release underneath network resources. + * + *
Example:{@code
+   * String[] events = {"s3:ObjectCreated:*", "s3:ObjectAccessed:*"};
+   * try (CloseableIterator> ci =
+   *     minioAsyncClient.listenBucketNotification(
+   *         ListenBucketNotificationArgs.builder()
+   *             .bucket("bucketName")
+   *             .prefix("")
+   *             .suffix("")
+   *             .events(events)
+   *             .build())) {
+   *   while (ci.hasNext()) {
+   *     NotificationRecords records = ci.next().get();
+   *     for (Event event : records.events()) {
+   *       System.out.println("Event " + event.eventType() + " occurred at "
+   *           + event.eventTime() + " for " + event.bucketName() + "/"
+   *           + event.objectName());
+   *     }
+   *   }
+   * }
+   * }
+ * + * @param args {@link ListenBucketNotificationArgs} object. + * @return {@code CloseableIterator>} - Lazy closable iterator + * contains event records. + * @throws ErrorResponseException thrown to indicate S3 service returned an error response. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws InvalidResponseException thrown to indicate S3 service returned invalid or no error + * response. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CloseableIterator> listenBucketNotification( + ListenBucketNotificationArgs args) + throws ErrorResponseException, InsufficientDataException, InternalException, + InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException, + ServerException, XmlParserException { + checkArgs(args); + + Multimap queryParams = + newMultimap("prefix", args.prefix(), "suffix", args.suffix()); + for (String event : args.events()) { + queryParams.put("events", event); + } + + Response response = null; + try { + response = executeGetAsync(args, null, queryParams).get(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + throwEncapsulatedException(e); + } + NotificationResultRecords result = new NotificationResultRecords(response); + return result.closeableIterator(); + } + + /** + * Selects content of an object by SQL expression. + * + *
Example:{@code
+   * String sqlExpression = "select * from S3Object";
+   * InputSerialization is =
+   *     new InputSerialization(null, false, null, null, FileHeaderInfo.USE, null, null,
+   *         null);
+   * OutputSerialization os =
+   *     new OutputSerialization(null, null, null, QuoteFields.ASNEEDED, null);
+   * SelectResponseStream stream =
+   *     minioAsyncClient.selectObjectContent(
+   *       SelectObjectContentArgs.builder()
+   *       .bucket("my-bucketname")
+   *       .object("my-objectname")
+   *       .sqlExpression(sqlExpression)
+   *       .inputSerialization(is)
+   *       .outputSerialization(os)
+   *       .requestProgress(true)
+   *       .build());
+   *
+   * byte[] buf = new byte[512];
+   * int bytesRead = stream.read(buf, 0, buf.length);
+   * System.out.println(new String(buf, 0, bytesRead, StandardCharsets.UTF_8));
+   *
+   * Stats stats = stream.stats();
+   * System.out.println("bytes scanned: " + stats.bytesScanned());
+   * System.out.println("bytes processed: " + stats.bytesProcessed());
+   * System.out.println("bytes returned: " + stats.bytesReturned());
+   *
+   * stream.close();
+   * }
+ * + * @param args instance of {@link SelectObjectContentArgs} + * @return {@link SelectResponseStream} - Contains filtered records and progress. + * @throws ErrorResponseException thrown to indicate S3 service returned an error response. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws InvalidResponseException thrown to indicate S3 service returned invalid or no error + * response. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public SelectResponseStream selectObjectContent(SelectObjectContentArgs args) + throws ErrorResponseException, InsufficientDataException, InternalException, + InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException, + ServerException, XmlParserException { + checkArgs(args); + args.validateSsec(this.baseUrl); + Response response = null; + try { + response = + executePostAsync( + args, + (args.ssec() != null) ? newMultimap(args.ssec().headers()) : null, + newMultimap("select", "", "select-type", "2"), + new SelectObjectContentRequest( + args.sqlExpression(), + args.requestProgress(), + args.inputSerialization(), + args.outputSerialization(), + args.scanStartRange(), + args.scanEndRange())) + .get(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + throwEncapsulatedException(e); + } + return new SelectResponseStream(response.body().byteStream()); + } + + /** + * Sets encryption configuration of a bucket. + * + *
Example:{@code
+   * CompletableFuture future = minioAsyncClient.setBucketEncryption(
+   *     SetBucketEncryptionArgs.builder().bucket("my-bucketname").config(config).build());
+   * }
+ * + * @param args {@link SetBucketEncryptionArgs} object. + * @return {@link CompletableFuture}<{@link Void}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture setBucketEncryption(SetBucketEncryptionArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executePutAsync(args, null, newMultimap("encryption", ""), args.config(), 0) + .thenAccept(response -> response.close()); + } + + /** + * Gets encryption configuration of a bucket. + * + *
Example:{@code
+   * CompletableFuture future =
+   *     minioAsyncClient.getBucketEncryption(
+   *         GetBucketEncryptionArgs.builder().bucket("my-bucketname").build());
+   * }
+ * + * @param args {@link GetBucketEncryptionArgs} object. + * @return {@link CompletableFuture}<{@link SseConfiguration}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture getBucketEncryption(GetBucketEncryptionArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executeGetAsync(args, null, newMultimap("encryption", "")) + .exceptionally( + e -> { + Throwable ex = e.getCause(); + + if (ex instanceof CompletionException) { + ex = ((CompletionException) ex).getCause(); + } + + if (ex instanceof ExecutionException) { + ex = ((ExecutionException) ex).getCause(); + } + + if (ex instanceof ErrorResponseException) { + if (((ErrorResponseException) ex) + .errorResponse() + .code() + .equals(SERVER_SIDE_ENCRYPTION_CONFIGURATION_NOT_FOUND_ERROR)) { + return null; + } + } + throw new CompletionException(ex); + }) + .thenApply( + response -> { + if (response == null) return new SseConfiguration(null); + try { + return Xml.unmarshal(SseConfiguration.class, response.body().charStream()); + } catch (XmlParserException e) { + throw new CompletionException(e); + } finally { + response.close(); + } + }); + } + + /** + * Deletes encryption configuration of a bucket. + * + *
Example:{@code
+   * CompletableFuture future = minioAsyncClient.deleteBucketEncryption(
+   *     DeleteBucketEncryptionArgs.builder().bucket("my-bucketname").build());
+   * }
+ * + * @param args {@link DeleteBucketEncryptionArgs} object. + * @return {@link CompletableFuture}<{@link Void}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture deleteBucketEncryption(DeleteBucketEncryptionArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executeDeleteAsync(args, null, newMultimap("encryption", "")) + .exceptionally( + e -> { + Throwable ex = e.getCause(); + + if (ex instanceof CompletionException) { + ex = ((CompletionException) ex).getCause(); + } + + if (ex instanceof ExecutionException) { + ex = ((ExecutionException) ex).getCause(); + } + + if (ex instanceof ErrorResponseException) { + if (((ErrorResponseException) ex) + .errorResponse() + .code() + .equals(SERVER_SIDE_ENCRYPTION_CONFIGURATION_NOT_FOUND_ERROR)) { + return null; + } + } + throw new CompletionException(ex); + }) + .thenAccept( + response -> { + if (response != null) response.close(); + }); + } + + /** + * Gets tags of a bucket. + * + *
Example:{@code
+   * CompletableFuture future =
+   *     minioAsyncClient.getBucketTags(GetBucketTagsArgs.builder().bucket("my-bucketname").build());
+   * }
+ * + * @param args {@link GetBucketTagsArgs} object. + * @return {@link CompletableFuture}<{@link Tags}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture getBucketTags(GetBucketTagsArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executeGetAsync(args, null, newMultimap("tagging", "")) + .exceptionally( + e -> { + Throwable ex = e.getCause(); + + if (ex instanceof CompletionException) { + ex = ((CompletionException) ex).getCause(); + } + + if (ex instanceof ExecutionException) { + ex = ((ExecutionException) ex).getCause(); + } + + if (ex instanceof ErrorResponseException) { + if (((ErrorResponseException) ex).errorResponse().code().equals("NoSuchTagSet")) { + return null; + } + } + throw new CompletionException(ex); + }) + .thenApply( + response -> { + if (response == null) return new Tags(); + try { + return Xml.unmarshal(Tags.class, response.body().charStream()); + } catch (XmlParserException e) { + throw new CompletionException(e); + } finally { + response.close(); + } + }); + } + + /** + * Sets tags to a bucket. + * + *
Example:{@code
+   * Map map = new HashMap<>();
+   * map.put("Project", "Project One");
+   * map.put("User", "jsmith");
+   * CompletableFuture future = minioAsyncClient.setBucketTags(
+   *     SetBucketTagsArgs.builder().bucket("my-bucketname").tags(map).build());
+   * }
+ * + * @param args {@link SetBucketTagsArgs} object. + * @return {@link CompletableFuture}<{@link Void}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture setBucketTags(SetBucketTagsArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executePutAsync(args, null, newMultimap("tagging", ""), args.tags(), 0) + .thenAccept(response -> response.close()); + } + + /** + * Deletes tags of a bucket. + * + *
Example:{@code
+   * CompletableFuture future = minioAsyncClient.deleteBucketTags(
+   *     DeleteBucketTagsArgs.builder().bucket("my-bucketname").build());
+   * }
+ * + * @param args {@link DeleteBucketTagsArgs} object. + * @return {@link CompletableFuture}<{@link Void}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture deleteBucketTags(DeleteBucketTagsArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executeDeleteAsync(args, null, newMultimap("tagging", "")) + .thenAccept(response -> response.close()); + } + + /** + * Gets tags of an object. + * + *
Example:{@code
+   * CompletableFuture future =
+   *     minioAsyncClient.getObjectTags(
+   *         GetObjectTagsArgs.builder().bucket("my-bucketname").object("my-objectname").build());
+   * }
+ * + * @param args {@link GetObjectTagsArgs} object. + * @return {@link CompletableFuture}<{@link Tags}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture getObjectTags(GetObjectTagsArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + Multimap queryParams = newMultimap("tagging", ""); + if (args.versionId() != null) queryParams.put("versionId", args.versionId()); + return executeGetAsync(args, null, queryParams) + .thenApply( + response -> { + try { + return Xml.unmarshal(Tags.class, response.body().charStream()); + } catch (XmlParserException e) { + throw new CompletionException(e); + } finally { + response.close(); + } + }); + } + + /** + * Sets tags to an object. + * + *
Example:{@code
+   * Map map = new HashMap<>();
+   * map.put("Project", "Project One");
+   * map.put("User", "jsmith");
+   * CompletableFuture future = minioAsyncClient.setObjectTags(
+   *     SetObjectTagsArgs.builder()
+   *         .bucket("my-bucketname")
+   *         .object("my-objectname")
+   *         .tags((map)
+   *         .build());
+   * }
+ * + * @param args {@link SetObjectTagsArgs} object. + * @return {@link CompletableFuture}<{@link Void}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture setObjectTags(SetObjectTagsArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + Multimap queryParams = newMultimap("tagging", ""); + if (args.versionId() != null) queryParams.put("versionId", args.versionId()); + return executePutAsync(args, null, queryParams, args.tags(), 0) + .thenAccept(response -> response.close()); + } + + /** + * Deletes tags of an object. + * + *
Example:{@code
+   * CompletableFuture future = minioAsyncClient.deleteObjectTags(
+   *     DeleteObjectTags.builder().bucket("my-bucketname").object("my-objectname").build());
+   * }
+ * + * @param args {@link DeleteObjectTagsArgs} object. + * @return {@link CompletableFuture}<{@link Void}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture deleteObjectTags(DeleteObjectTagsArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + Multimap queryParams = newMultimap("tagging", ""); + if (args.versionId() != null) queryParams.put("versionId", args.versionId()); + return executeDeleteAsync(args, null, queryParams).thenAccept(response -> response.close()); + } + + /** + * Uploads multiple objects in a single put call. It is done by creating intermediate TAR file + * optionally compressed which is uploaded to S3 service. + * + *
Example:{@code
+   * // Upload snowball objects.
+   * List objects = new ArrayList();
+   * objects.add(
+   *     new SnowballObject(
+   *         "my-object-one",
+   *         new ByteArrayInputStream("hello".getBytes(StandardCharsets.UTF_8)),
+   *         5,
+   *         null));
+   * objects.add(
+   *     new SnowballObject(
+   *         "my-object-two",
+   *         new ByteArrayInputStream("java".getBytes(StandardCharsets.UTF_8)),
+   *         4,
+   *         null));
+   * CompletableFuture future = minioAsyncClient.uploadSnowballObjects(
+   *     UploadSnowballObjectsArgs.builder().bucket("my-bucketname").objects(objects).build());
+   * }
+ * + * @param args {@link UploadSnowballObjectsArgs} object. + * @return {@link CompletableFuture}<{@link ObjectWriteResponse}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture uploadSnowballObjects( + UploadSnowballObjectsArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + + return CompletableFuture.supplyAsync( + () -> { + FileOutputStream fos = null; + BufferedOutputStream bos = null; + SnappyFramedOutputStream sos = null; + ByteArrayOutputStream baos = null; + TarArchiveOutputStream tarOutputStream = null; + + try { + OutputStream os = null; + if (args.stagingFilename() != null) { + fos = new FileOutputStream(args.stagingFilename()); + bos = new BufferedOutputStream(fos); + os = bos; + } else { + baos = new ByteArrayOutputStream(); + os = baos; + } + + if (args.compression()) { + sos = new SnappyFramedOutputStream(os); + os = sos; + } + + tarOutputStream = new TarArchiveOutputStream(os); + tarOutputStream.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX); + for (SnowballObject object : args.objects()) { + if (object.filename() != null) { + Path filePath = Paths.get(object.filename()); + TarArchiveEntry entry = new TarArchiveEntry(filePath.toFile(), object.name()); + tarOutputStream.putArchiveEntry(entry); + Files.copy(filePath, tarOutputStream); + } else { + TarArchiveEntry entry = new TarArchiveEntry(object.name()); + if (object.modificationTime() != null) { + entry.setModTime(Date.from(object.modificationTime().toInstant())); + } + entry.setSize(object.size()); + tarOutputStream.putArchiveEntry(entry); + ByteStreams.copy(object.stream(), tarOutputStream); + } + tarOutputStream.closeArchiveEntry(); + } + tarOutputStream.finish(); + } catch (IOException e) { + throw new CompletionException(e); + } finally { + try { + if (tarOutputStream != null) tarOutputStream.flush(); + if (sos != null) sos.flush(); + if (bos != null) bos.flush(); + if (fos != null) fos.flush(); + if (tarOutputStream != null) tarOutputStream.close(); + if (sos != null) sos.close(); + if (bos != null) bos.close(); + if (fos != null) fos.close(); + } catch (IOException e) { + throw new CompletionException(e); + } + } + return baos; + }) + .thenCompose( + baos -> { + Multimap headers = newMultimap(args.extraHeaders()); + headers.putAll(args.genHeaders()); + headers.put("X-Amz-Meta-Snowball-Auto-Extract", "true"); + + if (args.stagingFilename() == null) { + byte[] data = baos.toByteArray(); + try { + return putObjectAsync( + args.bucket(), + args.region(), + args.object(), + data, + data.length, + headers, + args.extraQueryParams()); + } catch (InsufficientDataException + | InternalException + | InvalidKeyException + | IOException + | NoSuchAlgorithmException + | XmlParserException e) { + throw new CompletionException(e); + } + } + + long length = Paths.get(args.stagingFilename()).toFile().length(); + if (length > ObjectWriteArgs.MAX_OBJECT_SIZE) { + throw new IllegalArgumentException( + "tarball size " + length + " is more than maximum allowed 5TiB"); + } + try (RandomAccessFile file = new RandomAccessFile(args.stagingFilename(), "r")) { + return putObjectAsync( + args.bucket(), + args.region(), + args.object(), + file, + length, + headers, + args.extraQueryParams()); + } catch (InsufficientDataException + | InternalException + | InvalidKeyException + | IOException + | NoSuchAlgorithmException + | XmlParserException e) { + throw new CompletionException(e); + } + }); + } + + public static Builder builder() { + return new Builder(); + } + + /** Argument builder of {@link MinioClient}. */ + public static final class Builder { + private HttpUrl baseUrl; + private String awsS3Prefix; + private String awsDomainSuffix; + private boolean awsDualstack; + private boolean useVirtualStyle; + + private String region; + private Provider provider; + private OkHttpClient httpClient; + private boolean closeHttpClient; + + private void setAwsInfo(String host, boolean https) { + this.awsS3Prefix = null; + this.awsDomainSuffix = null; + this.awsDualstack = false; + + if (!HttpUtils.HOSTNAME_REGEX.matcher(host).find()) return; + + if (HttpUtils.AWS_ELB_ENDPOINT_REGEX.matcher(host).find()) { + String[] tokens = host.split("\\.elb\\.amazonaws\\.com", 1)[0].split("\\."); + this.region = tokens[tokens.length - 1]; + return; + } + + if (!HttpUtils.AWS_ENDPOINT_REGEX.matcher(host).find()) return; + + if (!HttpUtils.AWS_S3_ENDPOINT_REGEX.matcher(host).find()) { + throw new IllegalArgumentException("invalid Amazon AWS host " + host); + } + + Matcher matcher = HttpUtils.AWS_S3_PREFIX_REGEX.matcher(host); + matcher.lookingAt(); + int end = matcher.end(); + + this.awsS3Prefix = host.substring(0, end); + if (this.awsS3Prefix.contains("s3-accesspoint") && !https) { + throw new IllegalArgumentException("use HTTPS scheme for host " + host); + } + + String[] tokens = host.substring(end).split("\\."); + awsDualstack = "dualstack".equals(tokens[0]); + if (awsDualstack) tokens = Arrays.copyOfRange(tokens, 1, tokens.length); + String regionInHost = null; + if (!tokens[0].equals("vpce") && !tokens[0].equals("amazonaws")) { + regionInHost = tokens[0]; + tokens = Arrays.copyOfRange(tokens, 1, tokens.length); + } + this.awsDomainSuffix = String.join(".", tokens); + + if (host.equals("s3-external-1.amazonaws.com")) regionInHost = "us-east-1"; + if (host.equals("s3-us-gov-west-1.amazonaws.com") + || host.equals("s3-fips-us-gov-west-1.amazonaws.com")) { + regionInHost = "us-gov-west-1"; + } + + if (regionInHost != null) this.region = regionInHost; + } + + private void setBaseUrl(HttpUrl url) { + this.baseUrl = url; + this.setAwsInfo(url.host(), url.isHttps()); + this.useVirtualStyle = this.awsDomainSuffix != null + || (url.host().endsWith("aliyuncs.com")) // NOTE: aliyun + || (url.host().endsWith("myqcloud.com") && url.host().startsWith("cos.")) // NOTE: tencent + ; + } + + public Builder endpoint(String endpoint) { + setBaseUrl(HttpUtils.getBaseUrl(endpoint)); + return this; + } + + public Builder endpoint(String endpoint, int port, boolean secure) { + HttpUrl url = HttpUtils.getBaseUrl(endpoint); + if (port < 1 || port > 65535) { + throw new IllegalArgumentException("port must be in range of 1 to 65535"); + } + url = url.newBuilder().port(port).scheme(secure ? "https" : "http").build(); + + setBaseUrl(url); + return this; + } + + public Builder endpoint(URL url) { + HttpUtils.validateNotNull(url, "url"); + return endpoint(HttpUrl.get(url)); + } + + public Builder endpoint(HttpUrl url) { + HttpUtils.validateNotNull(url, "url"); + HttpUtils.validateUrl(url); + setBaseUrl(url); + return this; + } + + public Builder region(String region) { + if (region != null && !HttpUtils.REGION_REGEX.matcher(region).find()) { + throw new IllegalArgumentException("invalid region " + region); + } + this.region = region; + return this; + } + + public Builder credentials(String accessKey, String secretKey) { + this.provider = new StaticProvider(accessKey, secretKey, null); + return this; + } + + public Builder credentialsProvider(Provider provider) { + this.provider = provider; + return this; + } + + public Builder httpClient(OkHttpClient httpClient) { + HttpUtils.validateNotNull(httpClient, "http client"); + this.httpClient = httpClient; + return this; + } + + public Builder httpClient(OkHttpClient httpClient, boolean close) { + HttpUtils.validateNotNull(httpClient, "http client"); + this.httpClient = httpClient; + this.closeHttpClient = close; + return this; + } + + public MinioAsyncClient build() { + HttpUtils.validateNotNull(this.baseUrl, "endpoint"); + + if (this.awsDomainSuffix != null + && this.awsDomainSuffix.endsWith(".cn") + && !this.awsS3Prefix.endsWith("s3-accelerate.") + && this.region == null) { + throw new IllegalArgumentException( + "Region missing in Amazon S3 China endpoint " + this.baseUrl); + } + + if (this.httpClient == null) { + this.closeHttpClient = true; + this.httpClient = + HttpUtils.newDefaultHttpClient( + DEFAULT_CONNECTION_TIMEOUT, DEFAULT_CONNECTION_TIMEOUT, DEFAULT_CONNECTION_TIMEOUT); + } + + return new MinioAsyncClient( + baseUrl, + awsS3Prefix, + awsDomainSuffix, + awsDualstack, + useVirtualStyle, + region, + provider, + httpClient, + closeHttpClient); + } + } +} diff --git a/plusone-oss-spring-boot2-test/src/main/java/xyz/zhouxy/plusone/oss/OSSType.java b/plusone-oss-spring-boot2-test/src/main/java/xyz/zhouxy/plusone/oss/OSSType.java new file mode 100644 index 0000000..ed555f2 --- /dev/null +++ b/plusone-oss-spring-boot2-test/src/main/java/xyz/zhouxy/plusone/oss/OSSType.java @@ -0,0 +1,32 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package xyz.zhouxy.plusone.oss; + +/** + * + * + *

+ * + *

+ * + *

+ * NOTE: + *

+ * @author ZhouXY + */ +public enum OSSType { + MINIO, ALIYUN, TENCENT, +} diff --git a/plusone-oss-spring-boot2-test/src/main/java/xyz/zhouxy/plusone/oss/PlusoneOssTestApplication.java b/plusone-oss-spring-boot2-test/src/main/java/xyz/zhouxy/plusone/oss/PlusoneOssTestApplication.java new file mode 100644 index 0000000..1e9fddd --- /dev/null +++ b/plusone-oss-spring-boot2-test/src/main/java/xyz/zhouxy/plusone/oss/PlusoneOssTestApplication.java @@ -0,0 +1,11 @@ +package xyz.zhouxy.plusone.oss; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class PlusoneOssTestApplication { + public static void main(String[] args) { + SpringApplication.run(PlusoneOssTestApplication.class, args); + } +} diff --git a/plusone-oss-spring-boot2-test/src/main/java/xyz/zhouxy/plusone/oss/PlusoneS3AutoConfiguration.java b/plusone-oss-spring-boot2-test/src/main/java/xyz/zhouxy/plusone/oss/PlusoneS3AutoConfiguration.java new file mode 100644 index 0000000..79f8cd1 --- /dev/null +++ b/plusone-oss-spring-boot2-test/src/main/java/xyz/zhouxy/plusone/oss/PlusoneS3AutoConfiguration.java @@ -0,0 +1,152 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package xyz.zhouxy.plusone.oss; + +import java.util.Objects; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.StringUtils; + +import io.minio.MinioAsyncClient; +import io.minio.MinioClient; +import xyz.zhouxy.plusone.commons.util.AssertTools; + +/** + * S3 配置 + * + * @author ZhouXY + */ +@Configuration +@EnableConfigurationProperties(S3Properties.class) +@ConditionalOnClass(value = { + MinioClient.class, + MinioAsyncClient.class, +}) +public class PlusoneS3AutoConfiguration { + + private final OSSType ossType; + private final Protocol protocol; + private final Integer port; + private final String endpoint; + private final String accessKey; + private final String secretKey; + private final String region; + + private final boolean secure; + + @Autowired + public PlusoneS3AutoConfiguration(S3Properties s3Properties) { + this(s3Properties.getType(), + s3Properties.getProtocol(), + s3Properties.getEndpoint(), + s3Properties.getPort(), + s3Properties.isSecure(), + s3Properties.getAccessKey(), + s3Properties.getSecretKey(), + s3Properties.getRegion()); + } + + private PlusoneS3AutoConfiguration(OSSType ossType, + Protocol protocol, String endpoint, Integer port, boolean secure, + String accessKey, String secretKey, String region) { + AssertTools.checkArgument(Objects.nonNull(ossType), "Property \"ossType\" cannot be null"); + AssertTools.checkArgument(StringUtils.hasText(endpoint), "Property \"endpoint\" must has text."); + AssertTools.checkArgument(StringUtils.hasText(accessKey), "Property \"accessKey\" must has text."); + AssertTools.checkArgument(StringUtils.hasText(secretKey), "Property \"secretKey\" must has text."); + AssertTools.checkArgument(StringUtils.hasText(region), "Property \"region\" must has text."); + + this.ossType = ossType; + + int i = endpoint.indexOf("://"); + if (i == -1) { + // endpoint 不带协议 + this.protocol = protocol != null ? protocol : Protocol.HTTP; + this.endpoint = endpoint; + } else { + // endpoint 带协议 + if (protocol != null) { + // protocol 不为空,则使用 protocol + this.protocol = protocol; + } else { + // protocol 为空,则使用 endpoint 中的协议 + String protocolInEndPoint = endpoint.substring(0, i); + this.protocol = Protocol.of(protocolInEndPoint); + } + // 去除协议部分 + this.endpoint = endpoint.substring(i + "://".length()); + } + this.port = port; + this.secure = secure; + this.accessKey = accessKey; + this.secretKey = secretKey; + this.region = region; + } + + public MinioClient ossClient() { + final MinioClient.Builder minioClientBuilder = MinioClient.builder(); + final String url = this.protocol.getCode() + "://" + this.endpoint; + if (this.port != null) { + minioClientBuilder.endpoint(url, this.port, this.secure); + } + else { + minioClientBuilder.endpoint(url); + } + return minioClientBuilder + .region(this.region) + .credentials(this.accessKey, this.secretKey) + .build(); + } + + /** + * @return the ossType + */ + public OSSType getOssType() { + return ossType; + } + + public Protocol getProtocol() { + return protocol; + } + + /** + * @return the port + */ + public Integer getPort() { + return port; + } + + public String getEndpoint() { + return endpoint; + } + + public String getAccessKey() { + return accessKey; + } + + public String getSecretKey() { + return secretKey; + } + + /** + * @return the region + */ + public String getRegion() { + return region; + } +} diff --git a/plusone-oss-spring-boot2-test/src/main/java/xyz/zhouxy/plusone/oss/Protocol.java b/plusone-oss-spring-boot2-test/src/main/java/xyz/zhouxy/plusone/oss/Protocol.java new file mode 100644 index 0000000..6c671e5 --- /dev/null +++ b/plusone-oss-spring-boot2-test/src/main/java/xyz/zhouxy/plusone/oss/Protocol.java @@ -0,0 +1,66 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package xyz.zhouxy.plusone.oss; + +import javax.annotation.Nonnull; + +import xyz.zhouxy.plusone.commons.base.IWithCode; + +/** + * + * + *

+ * + *

+ * + *

+ * NOTE: + *

+ * @author ZhouXY + */ +public enum Protocol implements IWithCode { + HTTP("http"), + HTTPS("https"), + + ; + + @Nonnull + final String code; + + static final Protocol[] values = { HTTP, HTTPS }; + + Protocol(@Nonnull String code) { + this.code = code; + } + + public static Protocol of(String code) { + for (Protocol protocol : values) { + if (protocol.equalsCode(code)) { + return protocol; + } + } + throw new IllegalArgumentException("Endpoint 中使用了无效的协议"); + } + + /** + * @return the code + */ + @Override + @Nonnull + public String getCode() { + return code; + } +} diff --git a/plusone-oss-spring-boot2-test/src/main/java/xyz/zhouxy/plusone/oss/S3Properties.java b/plusone-oss-spring-boot2-test/src/main/java/xyz/zhouxy/plusone/oss/S3Properties.java new file mode 100644 index 0000000..db603c9 --- /dev/null +++ b/plusone-oss-spring-boot2-test/src/main/java/xyz/zhouxy/plusone/oss/S3Properties.java @@ -0,0 +1,143 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package xyz.zhouxy.plusone.oss; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.util.StringUtils; + +import xyz.zhouxy.plusone.commons.util.AssertTools; + +/** + * S3 配置 + * + * @author ZhouXY + */ +@ConfigurationProperties("plusone.s3") +public class S3Properties { + + private OSSType type; + private Protocol protocol; + private String endpoint; + private Integer port; + private String accessKey; + private String secretKey; + private String region; + + /** + * @return the type + */ + public OSSType getType() { + return type; + } + + /** + * @param type the type to set + */ + public void setType(OSSType type) { + this.type = type; + } + + /** + * @return the protocol + */ + public Protocol getProtocol() { + return protocol; + } + + /** + * @param protocol the protocol to set + */ + public void setProtocol(Protocol protocol) { + this.protocol = protocol; + } + + /** + * @return the endpoint + */ + public String getEndpoint() { + return endpoint; + } + + /** + * @param endpoint the endpoint to set + */ + public void setEndpoint(String endpoint) { + AssertTools.checkArgument(StringUtils.hasText(endpoint), "Property \"endpoint\" must has text."); + this.endpoint = endpoint; + } + + /** + * @return the port + */ + public Integer getPort() { + return port; + } + + /** + * @param port the port to set + */ + public void setPort(Integer port) { + this.port = port; + } + + /** + * @return the accessKey + */ + public String getAccessKey() { + return accessKey; + } + + /** + * @param accessKey the accessKey to set + */ + public void setAccessKey(String accessKey) { + AssertTools.checkArgument(StringUtils.hasText(accessKey), "Property \"accessKey\" must has text."); + this.accessKey = accessKey; + } + + /** + * @return the secretKey + */ + public String getSecretKey() { + return secretKey; + } + + /** + * @param secretKey the secretKey to set + */ + public void setSecretKey(String secretKey) { + AssertTools.checkArgument(StringUtils.hasText(secretKey), "Property \"secretKey\" must has text."); + this.secretKey = secretKey; + } + + /** + * @return the region + */ + public String getRegion() { + return region; + } + + /** + * @param region the region to set + */ + public void setRegion(String region) { + this.region = region; + } + + public boolean isSecure() { + return this.protocol != null && this.protocol == Protocol.HTTPS; + } +} diff --git a/plusone-oss-spring-boot2-test/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/plusone-oss-spring-boot2-test/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 0000000..0c0689d --- /dev/null +++ b/plusone-oss-spring-boot2-test/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,37 @@ +{"properties": [ + { + "name": "plusone.s3.type", + "type": "java.lang.String", + "description": "A description for 'plusone.s3.type'" + }, + { + "name": "plusone.s3.protocol", + "type": "software.amazon.awssdk.core.Protocol", + "description": "A description for 'plusone.s3.protocol'" + }, + { + "name": "plusone.s3.endpoint", + "type": "java.lang.String", + "description": "A description for 'plusone.s3.endpoint'" + }, + { + "name": "plusone.s3.access-key", + "type": "java.lang.String", + "description": "A description for 'plusone.s3.access-key'" + }, + { + "name": "plusone.s3.secret-key", + "type": "java.lang.String", + "description": "A description for 'plusone.s3.secret-key'" + }, + { + "name": "plusone.s3.region", + "type": "java.lang.String", + "description": "A description for 'plusone.s3.region'" + }, + { + "name": "plusone.s3.port", + "type": "java.lang.String", + "description": "A description for 'plusone.s3.port'" + } +]} diff --git a/plusone-oss-spring-boot2-test/src/main/resources/META-INF/spring.factories b/plusone-oss-spring-boot2-test/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..e21453b --- /dev/null +++ b/plusone-oss-spring-boot2-test/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + xyz.zhouxy.plusone.oss.PlusoneS3AutoConfiguration diff --git a/plusone-oss-spring-boot2-test/src/main/resources/application.yaml b/plusone-oss-spring-boot2-test/src/main/resources/application.yaml new file mode 100644 index 0000000..5f51bc8 --- /dev/null +++ b/plusone-oss-spring-boot2-test/src/main/resources/application.yaml @@ -0,0 +1,23 @@ +spring: + application: + name: plusone-oss-test-application +plusone: + s3: + # type: minio + # protocol: http + # endpoint: zhouxy.xyz + # port: 9000 + # access-key: MPvs85pvZHLgwnIpP3jD + # secret-key: gEV7mzI3UXaxoR9Y5Zi39IYl3Vny2D0fE2JNYzWF + type: tencent + protocol: https + endpoint: cos.ap-chengdu.myqcloud.com + access-key: AKIDwkMjutlPq56lBPgsw4EMG3iEVbZfbIpD + secret-key: UkoiRyIj7HJD0cIpJesukS1OXcXKuIST + region: cn-north-1 + # type: aliyun + # protocol: https + # endpoint: oss-cn-beijing.aliyuncs.com + # access-key: LTAI5t8tdQCHpykg3BnXbez7 + # secret-key: zCwLzF2QovtWJRRbWHpIh1JbHIofIs + # region: cn-north-1 diff --git a/plusone-oss-spring-boot2-test/src/test/java/xyz/zhouxy/plusone/oss/PlusoneOssTestApplicationTests.java b/plusone-oss-spring-boot2-test/src/test/java/xyz/zhouxy/plusone/oss/PlusoneOssTestApplicationTests.java new file mode 100644 index 0000000..48f241b --- /dev/null +++ b/plusone-oss-spring-boot2-test/src/test/java/xyz/zhouxy/plusone/oss/PlusoneOssTestApplicationTests.java @@ -0,0 +1,56 @@ +package xyz.zhouxy.plusone.oss; + +import static org.junit.jupiter.api.Assertions.*; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import io.minio.GetPresignedObjectUrlArgs; +import io.minio.MinioClient; +import io.minio.http.Method; +import lombok.extern.slf4j.Slf4j; + +@SpringBootTest +@Slf4j +public class PlusoneOssTestApplicationTests { + + @Autowired + PlusoneS3AutoConfiguration s3Config; + + + @Test + void testListBuckets() throws Exception { + try (MinioClient minioClient = s3Config.ossClient()) { + minioClient.listBuckets().forEach(bucket -> { + log.info("Bucket - [name: {}, creationDate: {}]", bucket.name(), bucket.creationDate()); + }); + } catch (Exception e) { + log.error("测试失败", e); + throw e; + } + } + + @Test + void testMinIO() throws Exception { + try (MinioClient ossClient = s3Config.ossClient()) { + var url = ossClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder() + .bucket("code108-1300048601") + .object("plusone/表结构.xlsx") + .expiry(10, TimeUnit.MINUTES) + .method(Method.GET) + .build()); + logObj(url); + } catch (Exception e) { + log.info("测试失败", e); + throw e; + } + } + + static void logObj(Object obj) { + log.info("{}", obj); + } +} diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..b1bf160 --- /dev/null +++ b/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + xyz.zhouxy.plusone.oss + plusone-oss + 1.0-SNAPSHOT + pom + + plusone-oss-spring-boot2-test + + + + + xyz.zhouxy.plusone + plusone-commons + 1.0.0-alpha + + +