From f9f7e038fbf41c612a4af76fd895e965952c1a5e Mon Sep 17 00:00:00 2001 From: "Bala.FA" Date: Sun, 26 Jan 2025 10:23:58 +0530 Subject: [PATCH] Add new APIs * deleteBucketCors * getBucketCors * setBucketCors * getObjectAcl * getObjectAttributes * putObjectFanOut * promptObject Signed-off-by: Bala.FA --- api/src/main/java/io/minio/BaseArgs.java | 12 + .../java/io/minio/DeleteBucketCorsArgs.java | 30 ++ .../main/java/io/minio/GetBucketCorsArgs.java | 29 ++ .../main/java/io/minio/GetObjectAclArgs.java | 27 ++ .../io/minio/GetObjectAttributesArgs.java | 96 +++++ .../io/minio/GetObjectAttributesResponse.java | 42 ++ .../main/java/io/minio/MinioAsyncClient.java | 402 ++++++++++++++++++ api/src/main/java/io/minio/MinioClient.java | 274 ++++++++++++ api/src/main/java/io/minio/ObjectArgs.java | 12 - api/src/main/java/io/minio/PartSource.java | 11 + .../main/java/io/minio/PromptObjectArgs.java | 99 +++++ .../java/io/minio/PromptObjectResponse.java | 52 +++ .../java/io/minio/PutObjectFanOutArgs.java | 142 +++++++ .../java/io/minio/PutObjectFanOutEntry.java | 174 ++++++++ .../io/minio/PutObjectFanOutResponse.java | 79 ++++ api/src/main/java/io/minio/S3Base.java | 19 +- .../main/java/io/minio/SetBucketCorsArgs.java | 67 +++ .../io/minio/messages/AccessControlList.java | 7 +- .../minio/messages/AccessControlPolicy.java | 113 +++++ .../io/minio/messages/CORSConfiguration.java | 111 +++++ .../main/java/io/minio/messages/Checksum.java | 88 ++++ .../messages/GetObjectAttributesOutput.java | 146 +++++++ .../main/java/io/minio/messages/Grant.java | 20 +- .../main/java/io/minio/messages/Grantee.java | 37 ++ api/src/main/java/io/minio/messages/Part.java | 4 +- docs/API.md | 228 +++++++++- examples/DeleteBucketCors.java | 49 +++ examples/GetBucketCors.java | 51 +++ examples/GetObjectAcl.java | 52 +++ examples/GetObjectAttributes.java | 59 +++ examples/GetObjectResume.java | 55 +++ examples/PutObjectFanOut.java | 69 +++ examples/SetBucketCors.java | 75 ++++ functional/FunctionalTest.java | 228 ++++++++++ 34 files changed, 2922 insertions(+), 37 deletions(-) create mode 100644 api/src/main/java/io/minio/DeleteBucketCorsArgs.java create mode 100644 api/src/main/java/io/minio/GetBucketCorsArgs.java create mode 100644 api/src/main/java/io/minio/GetObjectAclArgs.java create mode 100644 api/src/main/java/io/minio/GetObjectAttributesArgs.java create mode 100644 api/src/main/java/io/minio/GetObjectAttributesResponse.java create mode 100644 api/src/main/java/io/minio/PromptObjectArgs.java create mode 100644 api/src/main/java/io/minio/PromptObjectResponse.java create mode 100644 api/src/main/java/io/minio/PutObjectFanOutArgs.java create mode 100644 api/src/main/java/io/minio/PutObjectFanOutEntry.java create mode 100644 api/src/main/java/io/minio/PutObjectFanOutResponse.java create mode 100644 api/src/main/java/io/minio/SetBucketCorsArgs.java create mode 100644 api/src/main/java/io/minio/messages/AccessControlPolicy.java create mode 100644 api/src/main/java/io/minio/messages/CORSConfiguration.java create mode 100644 api/src/main/java/io/minio/messages/Checksum.java create mode 100644 api/src/main/java/io/minio/messages/GetObjectAttributesOutput.java create mode 100644 examples/DeleteBucketCors.java create mode 100644 examples/GetBucketCors.java create mode 100644 examples/GetObjectAcl.java create mode 100644 examples/GetObjectAttributes.java create mode 100644 examples/GetObjectResume.java create mode 100644 examples/PutObjectFanOut.java create mode 100644 examples/SetBucketCors.java diff --git a/api/src/main/java/io/minio/BaseArgs.java b/api/src/main/java/io/minio/BaseArgs.java index 331fde95b..c8857ec55 100644 --- a/api/src/main/java/io/minio/BaseArgs.java +++ b/api/src/main/java/io/minio/BaseArgs.java @@ -26,6 +26,7 @@ import java.util.Map; import java.util.Objects; import java.util.function.Consumer; +import okhttp3.HttpUrl; /** Base argument class. */ public abstract class BaseArgs { @@ -42,6 +43,17 @@ public Multimap extraQueryParams() { return extraQueryParams; } + protected void checkSse(ServerSideEncryption sse, HttpUrl url) { + if (sse == null) { + return; + } + + if (sse.tlsRequired() && !url.isHttps()) { + throw new IllegalArgumentException( + sse + " operations must be performed over a secure connection."); + } + } + /** Base builder which builds arguments. */ public abstract static class Builder, A extends BaseArgs> { protected List> operations; diff --git a/api/src/main/java/io/minio/DeleteBucketCorsArgs.java b/api/src/main/java/io/minio/DeleteBucketCorsArgs.java new file mode 100644 index 000000000..b56679da8 --- /dev/null +++ b/api/src/main/java/io/minio/DeleteBucketCorsArgs.java @@ -0,0 +1,30 @@ +/* + * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2020 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; + +/** + * Argument class of {@link MinioAsyncClient#deleteBucketCors} and {@link + * MinioClient#deleteBucketCors}. + */ +public class DeleteBucketCorsArgs extends BucketArgs { + public static Builder builder() { + return new Builder(); + } + + /** Argument builder of {@link DeleteBucketCorsArgs}. */ + public static final class Builder extends BucketArgs.Builder {} +} diff --git a/api/src/main/java/io/minio/GetBucketCorsArgs.java b/api/src/main/java/io/minio/GetBucketCorsArgs.java new file mode 100644 index 000000000..477310038 --- /dev/null +++ b/api/src/main/java/io/minio/GetBucketCorsArgs.java @@ -0,0 +1,29 @@ +/* + * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2020 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; + +/** + * Argument class of {@link MinioAsyncClient#getBucketCors} and {@link MinioClient#getBucketCors}. + */ +public class GetBucketCorsArgs extends BucketArgs { + public static Builder builder() { + return new Builder(); + } + + /** Argument builder of {@link GetBucketCorsArgs}. */ + public static final class Builder extends BucketArgs.Builder {} +} diff --git a/api/src/main/java/io/minio/GetObjectAclArgs.java b/api/src/main/java/io/minio/GetObjectAclArgs.java new file mode 100644 index 000000000..840b25fc0 --- /dev/null +++ b/api/src/main/java/io/minio/GetObjectAclArgs.java @@ -0,0 +1,27 @@ +/* + * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2025 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; + +/** Argument class of {@link MinioAsyncClient#getObjectAcl} and {@link MinioClient#getObjectAcl}. */ +public class GetObjectAclArgs extends ObjectVersionArgs { + public static Builder builder() { + return new Builder(); + } + + /** Argument builder of {@link GetObjectAclArgs}. */ + public static final class Builder extends ObjectVersionArgs.Builder {} +} diff --git a/api/src/main/java/io/minio/GetObjectAttributesArgs.java b/api/src/main/java/io/minio/GetObjectAttributesArgs.java new file mode 100644 index 000000000..19bd9bbba --- /dev/null +++ b/api/src/main/java/io/minio/GetObjectAttributesArgs.java @@ -0,0 +1,96 @@ +/* + * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2025 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 java.util.Arrays; +import java.util.List; +import java.util.Objects; + +/** + * Argument class of {@link MinioAsyncClient#getObjectAttributes} and {@link + * MinioClient#getObjectAttributes}. + */ +public class GetObjectAttributesArgs extends ObjectReadArgs { + private List objectAttributes; + private Integer maxParts; + private Integer partNumberMarker; + + public List objectAttributes() { + return objectAttributes; + } + + public Integer maxParts() { + return maxParts; + } + + public Integer partNumberMarker() { + return partNumberMarker; + } + + public static Builder builder() { + return new Builder(); + } + + /** Argument builder of {@link GetObjectAttributesArgs}. */ + public static final class Builder + extends ObjectReadArgs.Builder { + @Override + protected void validate(GetObjectAttributesArgs args) { + super.validate(args); + validateNotNull(args.objectAttributes, "object attributes"); + } + + public Builder objectAttributes(String[] objectAttributes) { + operations.add( + args -> + args.objectAttributes = + objectAttributes == null ? null : Arrays.asList(objectAttributes)); + return this; + } + + public Builder objectAttributes(List objectAttributes) { + operations.add(args -> args.objectAttributes = objectAttributes); + return this; + } + + public Builder maxParts(Integer maxParts) { + operations.add(args -> args.maxParts = maxParts); + return this; + } + + public Builder partNumberMarker(Integer partNumberMarker) { + operations.add(args -> args.partNumberMarker = partNumberMarker); + return this; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof GetObjectAttributesArgs)) return false; + if (!super.equals(o)) return false; + GetObjectAttributesArgs that = (GetObjectAttributesArgs) o; + return Objects.equals(objectAttributes, that.objectAttributes) + && Objects.equals(maxParts, that.maxParts) + && Objects.equals(partNumberMarker, that.partNumberMarker); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), objectAttributes, maxParts, partNumberMarker); + } +} diff --git a/api/src/main/java/io/minio/GetObjectAttributesResponse.java b/api/src/main/java/io/minio/GetObjectAttributesResponse.java new file mode 100644 index 000000000..bbb4e95e6 --- /dev/null +++ b/api/src/main/java/io/minio/GetObjectAttributesResponse.java @@ -0,0 +1,42 @@ +/* + * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2025 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 io.minio.messages.GetObjectAttributesOutput; +import okhttp3.Headers; + +/** + * Response class of {@link MinioAsyncClient#getObjectAttributes} and {@link + * MinioClient#getObjectAttributes}. + */ +public class GetObjectAttributesResponse extends GenericResponse { + private GetObjectAttributesOutput result; + + public GetObjectAttributesResponse( + Headers headers, + String bucket, + String region, + String object, + GetObjectAttributesOutput result) { + super(headers, bucket, region, object); + this.result = result; + } + + public GetObjectAttributesOutput result() { + return result; + } +} diff --git a/api/src/main/java/io/minio/MinioAsyncClient.java b/api/src/main/java/io/minio/MinioAsyncClient.java index ce9430b4d..f72e52622 100644 --- a/api/src/main/java/io/minio/MinioAsyncClient.java +++ b/api/src/main/java/io/minio/MinioAsyncClient.java @@ -17,6 +17,8 @@ package io.minio; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonProcessingException; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import com.google.common.io.ByteStreams; @@ -32,11 +34,14 @@ import io.minio.errors.XmlParserException; import io.minio.http.HttpUtils; import io.minio.http.Method; +import io.minio.messages.AccessControlPolicy; import io.minio.messages.Bucket; +import io.minio.messages.CORSConfiguration; import io.minio.messages.CopyObjectResult; import io.minio.messages.CreateBucketConfiguration; import io.minio.messages.DeleteError; import io.minio.messages.DeleteObject; +import io.minio.messages.GetObjectAttributesOutput; import io.minio.messages.Item; import io.minio.messages.LegalHold; import io.minio.messages.LifecycleConfiguration; @@ -57,6 +62,7 @@ import java.io.IOException; import java.io.OutputStream; import java.io.RandomAccessFile; +import java.math.BigInteger; import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -66,8 +72,10 @@ import java.nio.file.StandardOpenOption; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; +import java.time.ZonedDateTime; import java.util.Arrays; import java.util.Date; +import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -78,6 +86,7 @@ import java.util.concurrent.ExecutionException; import java.util.regex.Matcher; import okhttp3.HttpUrl; +import okhttp3.MultipartBody; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; @@ -3151,6 +3160,238 @@ public CompletableFuture deleteObjectTags(DeleteObjectTagsArgs args) return executeDeleteAsync(args, null, queryParams).thenAccept(response -> response.close()); } + /** + * Gets CORS configuration of a bucket. + * + *
Example:{@code
+   * CompletableFuture future =
+   *     minioAsyncClient.getBucketCors(GetBucketCorsArgs.builder().bucket("my-bucketname").build());
+   * }
+ * + * @param args {@link GetBucketCorsArgs} object. + * @return {@link CompletableFuture}<{@link CORSConfiguration}> 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 getBucketCors(GetBucketCorsArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executeGetAsync(args, null, newMultimap("cors", "")) + .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 CORSConfiguration(null); + try { + return Xml.unmarshal(CORSConfiguration.class, response.body().charStream()); + } catch (XmlParserException e) { + throw new CompletionException(e); + } finally { + response.close(); + } + }); + } + + /** + * Sets CORS configuration to a bucket. + * + *
Example:{@code
+   * CORSConfiguration config =
+   *     new CORSConfiguration(
+   *         Arrays.asList(
+   *             new CORSConfiguration.CORSRule[] {
+   *               // Rule 1
+   *               new CORSConfiguration.CORSRule(
+   *                   Arrays.asList(new String[] {"*"}), // Allowed headers
+   *                   Arrays.asList(new String[] {"PUT", "POST", "DELETE"}), // Allowed methods
+   *                   Arrays.asList(new String[] {"http://www.example.com"}), // Allowed origins
+   *                   Arrays.asList(
+   *                       new String[] {"x-amz-server-side-encryption"}), // Expose headers
+   *                   null, // ID
+   *                   3000), // Maximum age seconds
+   *               // Rule 2
+   *               new CORSConfiguration.CORSRule(
+   *                   null, // Allowed headers
+   *                   Arrays.asList(new String[] {"GET"}), // Allowed methods
+   *                   Arrays.asList(new String[] {"*"}), // Allowed origins
+   *                   null, // Expose headers
+   *                   null, // ID
+   *                   null // Maximum age seconds
+   *                   )
+   *             }));
+   * CompletableFuture future = minioAsyncClient.setBucketCors(
+   *     SetBucketCorsArgs.builder().bucket("my-bucketname").config(config).build());
+   * }
+ * + * @param args {@link SetBucketCorsArgs} 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 setBucketCors(SetBucketCorsArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executePutAsync(args, null, newMultimap("cors", ""), args.config(), 0) + .thenAccept(response -> response.close()); + } + + /** + * Deletes CORS configuration of a bucket. + * + *
Example:{@code
+   * CompletableFuture future = minioAsyncClient.deleteBucketCors(
+   *     DeleteBucketCorsArgs.builder().bucket("my-bucketname").build());
+   * }
+ * + * @param args {@link DeleteBucketCorsArgs} 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 deleteBucketCors(DeleteBucketCorsArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executeDeleteAsync(args, null, newMultimap("cors", "")) + .thenAccept(response -> response.close()); + } + + /** + * Gets access control policy of an object. + * + *
Example:{@code
+   * CompletableFuture future =
+   *     minioAsyncClient.getObjectAcl(
+   *         GetObjectAclArgs.builder().bucket("my-bucketname").object("my-objectname").build());
+   * }
+ * + * @param args {@link GetObjectAclArgs} object. + * @return {@link CompletableFuture}<{@link AccessControlPolicy}> 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 getObjectAcl(GetObjectAclArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + Multimap queryParams = newMultimap("acl", ""); + if (args.versionId() != null) queryParams.put("versionId", args.versionId()); + return executeGetAsync(args, null, queryParams) + .thenApply( + response -> { + try { + return Xml.unmarshal(AccessControlPolicy.class, response.body().charStream()); + } catch (XmlParserException e) { + throw new CompletionException(e); + } finally { + response.close(); + } + }); + } + + /** + * Gets attributes of an object. + * + *
Example:{@code
+   * CompletableFuture future =
+   *     minioAsyncClient.getObjectAttributes(
+   *         GetObjectAttributesArgs.builder()
+   *             .bucket("my-bucketname")
+   *             .object("my-objectname")
+   *             .objectAttributes(
+   *                 new String[] {
+   *                   "ETag", "Checksum", "ObjectParts", "StorageClass", "ObjectSize"
+   *                 })
+   *             .build());
+   * }
+ * + * @param args {@link GetObjectAttributesArgs} object. + * @return {@link CompletableFuture}<{@link GetObjectAttributesResponse}> 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 getObjectAttributes( + GetObjectAttributesArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + + Multimap queryParams = newMultimap("attributes", ""); + if (args.versionId() != null) queryParams.put("versionId", args.versionId()); + + Multimap headers = HashMultimap.create(); + if (args.maxParts() != null) headers.put("x-amz-max-parts", args.maxParts().toString()); + if (args.partNumberMarker() != null) { + headers.put("x-amz-part-number-marker", args.partNumberMarker().toString()); + } + for (String attribute : args.objectAttributes()) { + if (attribute != null) headers.put("x-amz-object-attributes", attribute); + } + + return executeGetAsync(args, headers, queryParams) + .thenApply( + response -> { + try { + GetObjectAttributesOutput result = + Xml.unmarshal(GetObjectAttributesOutput.class, response.body().charStream()); + + String value = response.headers().get("x-amz-delete-marker"); + if (value != null) result.setDeleteMarker(Boolean.valueOf(value)); + value = response.headers().get("Last-Modified"); + if (value != null) { + result.setLastModified(ZonedDateTime.parse(value, Time.HTTP_HEADER_DATE_FORMAT)); + } + result.setVersionId(response.headers().get("x-amz-version-id")); + + return new GetObjectAttributesResponse( + response.headers(), args.bucket(), args.region(), args.object(), result); + } catch (XmlParserException e) { + throw new CompletionException(e); + } finally { + 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. @@ -3303,6 +3544,167 @@ public CompletableFuture uploadSnowballObjects( }); } + /** + * Uploads multiple objects with same content from single stream with optional metadata and tags. + * + *
Example:{@code
+   * Map map = new HashMap<>();
+   * map.put("Project", "Project One");
+   * map.put("User", "jsmith");
+   * CompletableFuture future =
+   *     minioAsyncClient.putObjectFanOut(
+   *         PutObjectFanOutArgs.builder().bucket("my-bucketname").stream(
+   *                 new ByteArrayInputStream("somedata".getBytes(StandardCharsets.UTF_8)), 8)
+   *             .entries(
+   *                 Arrays.asList(
+   *                     new PutObjectFanOutEntry[] {
+   *                       PutObjectFanOutEntry.builder().key("fan-out.0").build(),
+   *                       PutObjectFanOutEntry.builder().key("fan-out.1").tags(map).build()
+   *                     }))
+   *             .build());
+   * }
+ * + * @param args {@link PutObjectFanOutArgs} object. + * @return {@link CompletableFuture}<{@link PutObjectFanOutResponse}> object. + * @throws ErrorResponseException thrown to indicate presigned POST data failure. + * @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 putObjectFanOut(PutObjectFanOutArgs args) + throws ErrorResponseException, InsufficientDataException, InternalException, + InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + args.validateSse(this.baseUrl); + + return CompletableFuture.supplyAsync( + () -> { + // Build POST object data + String objectName = + "pan-out-" + + new BigInteger(32, random).toString(32) + + "-" + + System.currentTimeMillis(); + PostPolicy policy = + new PostPolicy(args.bucket(), ZonedDateTime.now().plusMinutes(15)); + policy.addEqualsCondition("key", objectName); + if (args.sse() != null) { + for (Map.Entry entry : args.sse().headers().entrySet()) { + policy.addEqualsCondition(entry.getKey(), entry.getValue()); + } + } + if (args.checksum() != null) { + for (Map.Entry entry : args.checksum().headers().entries()) { + policy.addEqualsCondition(entry.getKey(), entry.getValue()); + } + } + + try { + Map formData = this.getPresignedPostFormData(policy); + + // Build MultipartBody + 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", objectName); + multipartBuilder.addFormDataPart("x-minio-fanout-list", args.fanOutList()); + // "file" must be added at last. + multipartBuilder.addFormDataPart( + "file", + "fanout-content", + new HttpRequestBody(new PartSource(args.stream(), args.size()), null)); + + return multipartBuilder.build(); + } catch (JsonProcessingException e) { + throw new CompletionException(e); + } catch (ErrorResponseException + | InsufficientDataException + | InternalException + | InvalidKeyException + | InvalidResponseException + | IOException + | NoSuchAlgorithmException + | ServerException + | XmlParserException e) { + throw new CompletionException(e); + } + }) + .thenCompose( + body -> { + try { + return executePostAsync(args, null, null, body); + } catch (InsufficientDataException + | InternalException + | InvalidKeyException + | IOException + | NoSuchAlgorithmException + | XmlParserException e) { + throw new CompletionException(e); + } + }) + .thenApply( + response -> { + try { + JsonFactory jsonFactory = new JsonFactory(); + Iterator iterator = + objectMapper.readValues( + jsonFactory.createParser(response.body().byteStream()), + PutObjectFanOutResponse.Result.class); + List results = new LinkedList<>(); + iterator.forEachRemaining(results::add); + return new PutObjectFanOutResponse( + response.headers(), args.bucket(), args.region(), results); + } catch (IOException e) { + throw new CompletionException(e); + } finally { + response.close(); + } + }); + } + + /** + * Performs language model inference with the prompt and referenced object as context. + * + * @param args {@link PromptObjectArgs} object. + * @return {@link CompletableFuture}<{@link PromptObjectResponse}> 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 promptObject(PromptObjectArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + + Multimap queryParams = newMultimap("lambdaArn", args.lambdaArn()); + Multimap headers = + merge(newMultimap(args.headers()), newMultimap("Content-Type", "application/json")); + + Map promptArgs = args.promptArgs(); + if (promptArgs == null) promptArgs = new HashMap<>(); + promptArgs.put("prompt", args.prompt()); + byte[] data = objectMapper.writeValueAsString(promptArgs).getBytes(StandardCharsets.UTF_8); + + return executePostAsync(args, headers, queryParams, data) + .thenApply( + response -> { + return new PromptObjectResponse( + response.headers(), + args.bucket(), + args.region(), + args.object(), + response.body().byteStream()); + }); + } + public static Builder builder() { return new Builder(); } diff --git a/api/src/main/java/io/minio/MinioClient.java b/api/src/main/java/io/minio/MinioClient.java index dbef78c11..3b2bf5e19 100644 --- a/api/src/main/java/io/minio/MinioClient.java +++ b/api/src/main/java/io/minio/MinioClient.java @@ -26,7 +26,9 @@ import io.minio.errors.InvalidResponseException; import io.minio.errors.ServerException; import io.minio.errors.XmlParserException; +import io.minio.messages.AccessControlPolicy; import io.minio.messages.Bucket; +import io.minio.messages.CORSConfiguration; import io.minio.messages.DeleteError; import io.minio.messages.Item; import io.minio.messages.LifecycleConfiguration; @@ -2275,6 +2277,203 @@ public void deleteObjectTags(DeleteObjectTagsArgs args) } } + /** + * Gets CORS configuration of a bucket. + * + *
Example:{@code
+   * CORSConfiguration config =
+   *     minioClient.getBucketCors(GetBucketCorsArgs.builder().bucket("my-bucketname").build());
+   * }
+ * + * @param args {@link GetBucketCorsArgs} object. + * @return {@link CORSConfiguration} - CORSConfiguration. + * @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 CORSConfiguration getBucketCors(GetBucketCorsArgs args) + throws ErrorResponseException, InsufficientDataException, InternalException, + InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException, + ServerException, XmlParserException { + try { + return asyncClient.getBucketCors(args).get(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + asyncClient.throwEncapsulatedException(e); + return null; + } + } + + /** + * Sets CORS configuration to a bucket. + * + *
Example:{@code
+   * CORSConfiguration config =
+   *     new CORSConfiguration(
+   *         Arrays.asList(
+   *             new CORSConfiguration.CORSRule[] {
+   *               // Rule 1
+   *               new CORSConfiguration.CORSRule(
+   *                   Arrays.asList(new String[] {"*"}), // Allowed headers
+   *                   Arrays.asList(new String[] {"PUT", "POST", "DELETE"}), // Allowed methods
+   *                   Arrays.asList(new String[] {"http://www.example.com"}), // Allowed origins
+   *                   Arrays.asList(
+   *                       new String[] {"x-amz-server-side-encryption"}), // Expose headers
+   *                   null, // ID
+   *                   3000), // Maximum age seconds
+   *               // Rule 2
+   *               new CORSConfiguration.CORSRule(
+   *                   null, // Allowed headers
+   *                   Arrays.asList(new String[] {"GET"}), // Allowed methods
+   *                   Arrays.asList(new String[] {"*"}), // Allowed origins
+   *                   null, // Expose headers
+   *                   null, // ID
+   *                   null // Maximum age seconds
+   *                   )
+   *             }));
+   * minioClient.setBucketCors(
+   *     SetBucketCorsArgs.builder().bucket("my-bucketname").config(config).build());
+   * }
+ * + * @param args {@link SetBucketCorsArgs} 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 void setBucketCors(SetBucketCorsArgs args) + throws ErrorResponseException, InsufficientDataException, InternalException, + InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException, + ServerException, XmlParserException { + try { + asyncClient.setBucketCors(args).get(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + asyncClient.throwEncapsulatedException(e); + } + } + + /** + * Deletes CORS configuration of a bucket. + * + *
Example:{@code
+   * minioClient.deleteBucketCors(DeleteBucketCorsArgs.builder().bucket("my-bucketname").build());
+   * }
+ * + * @param args {@link DeleteBucketCorsArgs} 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 void deleteBucketCors(DeleteBucketCorsArgs args) + throws ErrorResponseException, InsufficientDataException, InternalException, + InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException, + ServerException, XmlParserException { + try { + asyncClient.deleteBucketCors(args).get(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + asyncClient.throwEncapsulatedException(e); + } + } + + /** + * Gets access control policy of an object. + * + *
Example:{@code
+   * AccessControlPolicy policy =
+   *     minioClient.getObjectAcl(
+   *         GetObjectAclArgs.builder().bucket("my-bucketname").object("my-objectname").build());
+   * }
+ * + * @param args {@link GetObjectAclArgs} object. + * @return {@link AccessControlPolicy} - Access control policy 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 AccessControlPolicy getObjectAcl(GetObjectAclArgs args) + throws ErrorResponseException, InsufficientDataException, InternalException, + InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException, + ServerException, XmlParserException { + try { + return asyncClient.getObjectAcl(args).get(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + asyncClient.throwEncapsulatedException(e); + return null; + } + } + + /** + * Gets attributes of an object. + * + *
Example:{@code
+   * GetObjectAttributesResponse response =
+   *     minioClient.getObjectAttributes(
+   *         GetObjectAttributesArgs.builder()
+   *             .bucket("my-bucketname")
+   *             .object("my-objectname")
+   *             .objectAttributes(
+   *                 new String[] {
+   *                   "ETag", "Checksum", "ObjectParts", "StorageClass", "ObjectSize"
+   *                 })
+   *             .build());
+   * }
+ * + * @param args {@link GetObjectAttributesArgs} object. + * @return {@link GetObjectAttributesResponse} - Response 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 GetObjectAttributesResponse getObjectAttributes(GetObjectAttributesArgs args) + throws ErrorResponseException, InsufficientDataException, InternalException, + InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException, + ServerException, XmlParserException { + try { + return asyncClient.getObjectAttributes(args).get(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + asyncClient.throwEncapsulatedException(e); + return null; + } + } + /** * Uploads multiple objects in a single put call. It is done by creating intermediate TAR file * optionally compressed which is uploaded to S3 service. @@ -2323,6 +2522,81 @@ public ObjectWriteResponse uploadSnowballObjects(UploadSnowballObjectsArgs args) } } + /** + * Uploads multiple objects with same content from single stream with optional metadata and tags. + * + *
Example:{@code
+   * Map map = new HashMap<>();
+   * map.put("Project", "Project One");
+   * map.put("User", "jsmith");
+   * PutObjectFanOutResponse future =
+   *     minioClient.putObjectFanOut(
+   *         PutObjectFanOutArgs.builder().bucket("my-bucketname").stream(
+   *                 new ByteArrayInputStream("somedata".getBytes(StandardCharsets.UTF_8)), 8)
+   *             .entries(
+   *                 Arrays.asList(
+   *                     new PutObjectFanOutEntry[] {
+   *                       PutObjectFanOutEntry.builder().key("fan-out.0").build(),
+   *                       PutObjectFanOutEntry.builder().key("fan-out.1").tags(map).build()
+   *                     }))
+   *             .build());
+   * }
+ * + * @param args {@link PutObjectFanOutArgs} object. + * @return {@link PutObjectFanOutResponse} 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 PutObjectFanOutResponse putObjectFanOut(PutObjectFanOutArgs args) + throws ErrorResponseException, InsufficientDataException, InternalException, + InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException, + ServerException, XmlParserException { + try { + return asyncClient.putObjectFanOut(args).get(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + asyncClient.throwEncapsulatedException(e); + return null; + } + } + + /** + * Performs language model inference with the prompt and referenced object as context. + * + * @param args {@link PromptObjectArgs} object. + * @return {@link PromptObjectResponse} 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 PromptObjectResponse promptObject(PromptObjectArgs args) + throws ErrorResponseException, InsufficientDataException, InternalException, + InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException, + ServerException, XmlParserException { + try { + return asyncClient.promptObject(args).get(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + asyncClient.throwEncapsulatedException(e); + return null; + } + } + /** * Sets HTTP connect, write and read timeouts. A value of 0 means no timeout, otherwise values * must be between 1 and Integer.MAX_VALUE when converted to milliseconds. diff --git a/api/src/main/java/io/minio/ObjectArgs.java b/api/src/main/java/io/minio/ObjectArgs.java index 1d7759338..6ec9c4f46 100644 --- a/api/src/main/java/io/minio/ObjectArgs.java +++ b/api/src/main/java/io/minio/ObjectArgs.java @@ -17,7 +17,6 @@ package io.minio; import java.util.Objects; -import okhttp3.HttpUrl; /** Base argument class holds object name and version ID along with bucket information. */ public abstract class ObjectArgs extends BucketArgs { @@ -27,17 +26,6 @@ public String object() { return objectName; } - protected void checkSse(ServerSideEncryption sse, HttpUrl url) { - if (sse == null) { - return; - } - - if (sse.tlsRequired() && !url.isHttps()) { - throw new IllegalArgumentException( - sse + " operations must be performed over a secure connection."); - } - } - /** Base argument builder class for {@link ObjectArgs}. */ public abstract static class Builder, A extends ObjectArgs> extends BucketArgs.Builder { diff --git a/api/src/main/java/io/minio/PartSource.java b/api/src/main/java/io/minio/PartSource.java index ff6101669..b18581eb5 100644 --- a/api/src/main/java/io/minio/PartSource.java +++ b/api/src/main/java/io/minio/PartSource.java @@ -42,6 +42,8 @@ class PartSource { private ByteBufferStream[] buffers; + private InputStream inputStream; + private PartSource(int partNumber, long size, String md5Hash, String sha256Hash) { this.partNumber = partNumber; this.size = size; @@ -67,6 +69,11 @@ public PartSource( this.buffers = Objects.requireNonNull(buffers, "buffers must not be null"); } + public PartSource(@Nonnull InputStream inputStream, long size) { + this(0, size, null, null); + this.inputStream = Objects.requireNonNull(inputStream, "input stream must not be null"); + } + public int partNumber() { return this.partNumber; } @@ -89,6 +96,10 @@ public Source source() throws IOException { return Okio.source(Channels.newInputStream(this.file.getChannel())); } + if (this.inputStream != null) { + return Okio.source(this.inputStream); + } + InputStream stream = buffers[0].inputStream(); if (buffers.length == 1) return Okio.source(stream); diff --git a/api/src/main/java/io/minio/PromptObjectArgs.java b/api/src/main/java/io/minio/PromptObjectArgs.java new file mode 100644 index 000000000..f092f4c65 --- /dev/null +++ b/api/src/main/java/io/minio/PromptObjectArgs.java @@ -0,0 +1,99 @@ +/* + * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2025 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 java.util.Map; +import java.util.Objects; + +/** Argument class of {@link MinioAsyncClient#promptObject} and {@link MinioClient#promptObject}. */ +public class PromptObjectArgs extends ObjectArgs { + private String prompt; + private String lambdaArn; + private Map promptArgs; + private Map headers; + + public String prompt() { + return prompt; + } + + public String lambdaArn() { + return lambdaArn; + } + + public Map promptArgs() { + return promptArgs; + } + + public Map headers() { + return headers; + } + + public static Builder builder() { + return new Builder(); + } + + /** Argument builder of {@link PromptObjectArgs}. */ + public static final class Builder extends ObjectArgs.Builder { + @Override + protected void validate(PromptObjectArgs args) { + super.validate(args); + validateNotEmptyString(args.prompt, "prompt"); + validateNotEmptyString(args.lambdaArn, "lambda ARN"); + validateNotNull(args.promptArgs, "prompt argument"); + } + + public Builder offset(String prompt) { + validateNotEmptyString(prompt, "prompt"); + operations.add(args -> args.prompt = prompt); + return this; + } + + public Builder lambdaArn(String lambdaArn) { + validateNotEmptyString(lambdaArn, "lambda ARN"); + operations.add(args -> args.lambdaArn = lambdaArn); + return this; + } + + public Builder promptArgs(Map promptArgs) { + validateNotNull(promptArgs, "prompt argument"); + operations.add(args -> args.promptArgs = promptArgs); + return this; + } + + public Builder headers(Map headers) { + operations.add(args -> args.headers = headers); + return this; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PromptObjectArgs)) return false; + if (!super.equals(o)) return false; + PromptObjectArgs that = (PromptObjectArgs) o; + return Objects.equals(prompt, that.prompt) + && Objects.equals(lambdaArn, that.lambdaArn) + && Objects.equals(promptArgs, that.promptArgs) + && Objects.equals(headers, that.headers); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), prompt, lambdaArn, promptArgs, headers); + } +} diff --git a/api/src/main/java/io/minio/PromptObjectResponse.java b/api/src/main/java/io/minio/PromptObjectResponse.java new file mode 100644 index 000000000..0d501850b --- /dev/null +++ b/api/src/main/java/io/minio/PromptObjectResponse.java @@ -0,0 +1,52 @@ +/* + * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2025 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 java.io.FilterInputStream; +import java.io.InputStream; +import okhttp3.Headers; + +/** + * Response class of {@link MinioAsyncClient#promptObject} and {@link MinioClient#promptObject}. + * This class is {@link InputStream} interface compatible and it must be closed after use to release + * underneath network resources. + */ +public class PromptObjectResponse extends FilterInputStream { + private GenericResponse response; + + public PromptObjectResponse( + Headers headers, String bucket, String region, String object, InputStream body) { + super(body); + this.response = new GenericResponse(headers, bucket, region, object); + } + + public Headers headers() { + return response.headers(); + } + + public String bucket() { + return response.bucket(); + } + + public String region() { + return response.region(); + } + + public String object() { + return response.object(); + } +} diff --git a/api/src/main/java/io/minio/PutObjectFanOutArgs.java b/api/src/main/java/io/minio/PutObjectFanOutArgs.java new file mode 100644 index 000000000..64ad1cb60 --- /dev/null +++ b/api/src/main/java/io/minio/PutObjectFanOutArgs.java @@ -0,0 +1,142 @@ +/* + * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2025 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.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import io.minio.messages.Checksum; +import java.io.InputStream; +import java.util.List; +import java.util.Objects; +import okhttp3.HttpUrl; + +/** + * Argument class of {@link MinioAsyncClient#putObjectFanOut} and {@link + * MinioClient#putObjectFanOut}. + */ +public class PutObjectFanOutArgs extends BucketArgs { + private static final ObjectMapper objectMapper = + JsonMapper.builder() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true) + .build(); + + private InputStream stream; + private long size; + private List entries = null; + private Checksum checksum; + private ServerSideEncryption sse; + + public InputStream stream() { + return stream; + } + + public long size() { + return size; + } + + public List entries() { + return entries; + } + + public Checksum checksum() { + return checksum; + } + + public ServerSideEncryption sse() { + return sse; + } + + public String fanOutList() throws JsonProcessingException { + if (entries == null) return null; + + StringBuilder builder = new StringBuilder(); + for (PutObjectFanOutEntry entry : entries) { + builder.append(objectMapper.writeValueAsString(entry)); + } + return builder.toString(); + } + + public void validateSse(HttpUrl url) { + checkSse(sse, url); + } + + public static Builder builder() { + return new Builder(); + } + + /** Argument builder of {@link PutObjectFanOutArgs}. */ + public static final class Builder extends BucketArgs.Builder { + @Override + protected void validate(PutObjectFanOutArgs args) { + super.validate(args); + validateNotNull(args.stream, "stream"); + validateNotNull(args.entries, "fan-out entries"); + } + + public Builder stream(InputStream stream, long size) { + validateNotNull(stream, "stream"); + if (size < 0) { + throw new IllegalArgumentException("invalid stream size " + size); + } + if (size > ObjectWriteArgs.MAX_PART_SIZE) { + throw new IllegalArgumentException( + "size " + size + " is not supported; maximum allowed 5GiB"); + } + + operations.add(args -> args.stream = stream); + operations.add(args -> args.size = size); + return this; + } + + public Builder entries(List entries) { + operations.add(args -> args.entries = entries); + return this; + } + + public Builder checksum(Checksum checksum) { + operations.add(args -> args.checksum = checksum); + return this; + } + + public Builder sse(ServerSideEncryption sse) { + operations.add(args -> args.sse = sse); + return this; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PutObjectFanOutArgs)) return false; + if (!super.equals(o)) return false; + PutObjectFanOutArgs that = (PutObjectFanOutArgs) o; + return Objects.equals(stream, that.stream) + && size == that.size + && Objects.equals(entries, that.entries) + && Objects.equals(checksum, that.checksum) + && Objects.equals(sse, that.sse); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), stream, size, entries, checksum, sse); + } +} diff --git a/api/src/main/java/io/minio/PutObjectFanOutEntry.java b/api/src/main/java/io/minio/PutObjectFanOutEntry.java new file mode 100644 index 000000000..9bf029b97 --- /dev/null +++ b/api/src/main/java/io/minio/PutObjectFanOutEntry.java @@ -0,0 +1,174 @@ +/* + * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2025 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.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.minio.messages.RetentionMode; +import io.minio.messages.Tags; +import java.time.ZonedDateTime; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; + +/** An object entry for {@link PutObjectFanOutArgs}. */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class PutObjectFanOutEntry extends BaseArgs { + @JsonProperty("key") + String key; + + @JsonProperty("metadata") + Map userMetadata; + + @JsonProperty("tags") + Map tags; + + @JsonProperty("contentType") + String contentType; + + @JsonProperty("contentEncoding") + String contentEncoding; + + @JsonProperty("contentDisposition") + String contentDisposition; + + @JsonProperty("contentLanguage") + String contentLanguage; + + @JsonProperty("cacheControl") + String cacheControl; + + @JsonProperty("retention") + RetentionMode retention; + + @JsonProperty("retainUntil") + ZonedDateTime retainUntilDate; + + public static Builder builder() { + return new Builder(); + } + + /** Argument builder of {@link PutObjectFanOutEntry}. */ + public static final class Builder extends BaseArgs.Builder { + @Override + protected void validate(PutObjectFanOutEntry args) { + validateNotEmptyString(args.key, "key"); + } + + public Builder key(String key) { + validateNotEmptyString(key, "key"); + operations.add(args -> args.key = key); + return this; + } + + public Builder userMetadata(Map userMetadata) { + final Map userMetadataCopy = new HashMap<>(); + if (userMetadata != null) { + for (Map.Entry entry : userMetadata.entrySet()) { + String key = entry.getKey(); + userMetadataCopy.put( + (key.toLowerCase(Locale.US).startsWith("x-amz-meta-") ? "" : "x-amz-meta-") + key, + entry.getValue()); + } + } + + operations.add(args -> args.userMetadata = userMetadata == null ? null : userMetadataCopy); + return this; + } + + public Builder tags(Map map) { + Tags.newObjectTags(map); + operations.add(args -> args.tags = map); + return this; + } + + public Builder tags(Tags tags) { + operations.add(args -> args.tags = tags == null ? null : tags.get()); + return this; + } + + public Builder contentType(String contentType) { + operations.add(args -> args.contentType = contentType); + return this; + } + + public Builder contentEncoding(String contentEncoding) { + operations.add(args -> args.contentEncoding = contentEncoding); + return this; + } + + public Builder contentDisposition(String contentDisposition) { + operations.add(args -> args.contentDisposition = contentDisposition); + return this; + } + + public Builder contentLanguage(String contentLanguage) { + operations.add(args -> args.contentLanguage = contentLanguage); + return this; + } + + public Builder cacheControl(String cacheControl) { + operations.add(args -> args.cacheControl = cacheControl); + return this; + } + + public Builder retention(RetentionMode retention) { + operations.add(args -> args.retention = retention); + return this; + } + + public Builder retainUntilDate(ZonedDateTime retainUntilDate) { + operations.add(args -> args.retainUntilDate = retainUntilDate); + return this; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PutObjectFanOutEntry)) return false; + if (!super.equals(o)) return false; + PutObjectFanOutEntry that = (PutObjectFanOutEntry) o; + return Objects.equals(key, that.key) + && Objects.equals(userMetadata, that.userMetadata) + && Objects.equals(tags, that.tags) + && Objects.equals(contentType, that.contentType) + && Objects.equals(contentEncoding, that.contentEncoding) + && Objects.equals(contentDisposition, that.contentDisposition) + && Objects.equals(contentLanguage, that.contentLanguage) + && Objects.equals(cacheControl, that.cacheControl) + && Objects.equals(retention, that.retention) + && Objects.equals(retainUntilDate, that.retainUntilDate); + } + + @Override + public int hashCode() { + return Objects.hash( + super.hashCode(), + key, + userMetadata, + tags, + contentType, + contentEncoding, + contentDisposition, + contentLanguage, + cacheControl, + retention, + retainUntilDate); + } +} diff --git a/api/src/main/java/io/minio/PutObjectFanOutResponse.java b/api/src/main/java/io/minio/PutObjectFanOutResponse.java new file mode 100644 index 000000000..e1f28be06 --- /dev/null +++ b/api/src/main/java/io/minio/PutObjectFanOutResponse.java @@ -0,0 +1,79 @@ +/* + * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2025 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.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import okhttp3.Headers; + +/** + * Response class of {@link MinioAsyncClient#putObjectFanOut} and {@link + * MinioClient#putObjectFanOut}. + */ +public class PutObjectFanOutResponse extends GenericUploadResponse { + private List results; + + public PutObjectFanOutResponse( + Headers headers, String bucket, String region, List results) { + super(headers, bucket, region, null, null); + this.results = results; + } + + public List results() { + return results; + } + + public static class Result { + @JsonProperty("key") + private String key; + + @JsonProperty("etag") + private String etag; + + @JsonProperty("versionId") + private String versionId; + + @JsonProperty("lastModified") + private String lastModified; + // private ResponseDate lastModified; + + @JsonProperty("error") + private String error; + + public Result() {} + + public String key() { + return key; + } + + public String etag() { + return etag; + } + + public String versionId() { + return versionId; + } + + public String lastModified() { + return lastModified; + } + + public String error() { + return error; + } + } +} diff --git a/api/src/main/java/io/minio/S3Base.java b/api/src/main/java/io/minio/S3Base.java index fbfa6b7c8..324637ccb 100644 --- a/api/src/main/java/io/minio/S3Base.java +++ b/api/src/main/java/io/minio/S3Base.java @@ -70,6 +70,7 @@ import java.security.InvalidKeyException; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import java.time.ZonedDateTime; import java.util.Arrays; import java.util.Collections; @@ -79,6 +80,7 @@ import java.util.Locale; import java.util.Map; import java.util.NoSuchElementException; +import java.util.Random; import java.util.Scanner; import java.util.Set; import java.util.concurrent.CompletableFuture; @@ -121,12 +123,18 @@ public abstract class S3Base implements AutoCloseable { protected static final int MAX_BUCKET_POLICY_SIZE = 20 * 1024; protected static final String US_EAST_1 = "us-east-1"; protected final Map regionCache = new ConcurrentHashMap<>(); + protected static final Random random = new Random(new SecureRandom().nextLong()); + protected static final ObjectMapper objectMapper = + JsonMapper.builder() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true) + .build(); private static final String RETRY_HEAD = "RetryHead"; private static final String END_HTTP = "----------END-HTTP----------"; private static final String UPLOAD_ID = "uploadId"; private static final Set TRACE_QUERY_PARAMS = - ImmutableSet.of("retention", "legal-hold", "tagging", UPLOAD_ID); + ImmutableSet.of("retention", "legal-hold", "tagging", UPLOAD_ID, "acl", "attributes"); private PrintWriter traceStream; private String userAgent = MinioProperties.INSTANCE.getDefaultUserAgent(); @@ -482,6 +490,10 @@ protected Request createRequest( requestBuilder.header("Accept-Encoding", "identity"); requestBuilder.header("User-Agent", this.userAgent); + if (body != null && body instanceof RequestBody) { + return requestBuilder.method(method.toString(), (RequestBody) body).build(); + } + String md5Hash = Digest.ZERO_MD5_HASH; if (body != null) { md5Hash = (body instanceof byte[]) ? Digest.md5Hash((byte[]) body, length) : null; @@ -566,7 +578,8 @@ protected CompletableFuture executeAsync( throws InsufficientDataException, InternalException, InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { boolean traceRequestBody = false; - if (body != null && !(body instanceof PartSource || body instanceof byte[])) { + if (body != null + && !(body instanceof PartSource || body instanceof byte[] || body instanceof RequestBody)) { byte[] bytes; if (body instanceof CharSequence) { bytes = body.toString().getBytes(StandardCharsets.UTF_8); @@ -586,7 +599,7 @@ protected CompletableFuture executeAsync( HttpUrl url = buildUrl(method, bucketName, objectName, region, queryParamMap); Credentials creds = (provider == null) ? null : provider.fetch(); Request req = createRequest(url, method, headers, body, length, creds); - if (creds != null) { + if (!(body != null && body instanceof RequestBody) && creds != null) { req = Signer.signV4S3( req, diff --git a/api/src/main/java/io/minio/SetBucketCorsArgs.java b/api/src/main/java/io/minio/SetBucketCorsArgs.java new file mode 100644 index 000000000..11d6b59ee --- /dev/null +++ b/api/src/main/java/io/minio/SetBucketCorsArgs.java @@ -0,0 +1,67 @@ +/* + * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2020 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 io.minio.messages.CORSConfiguration; +import java.util.Objects; + +/** + * Argument class of {@link MinioAsyncClient#setBucketCors} and {@link MinioClient#setBucketCors}. + */ +public class SetBucketCorsArgs extends BucketArgs { + private CORSConfiguration config; + + public CORSConfiguration config() { + return config; + } + + public static Builder builder() { + return new Builder(); + } + + /** Argument builder of {@link SetBucketCorsArgs}. */ + public static final class Builder extends BucketArgs.Builder { + private void validateCors(CORSConfiguration config) { + validateNotNull(config, "CORS configuration"); + } + + protected void validate(SetBucketCorsArgs args) { + super.validate(args); + validateCors(args.config); + } + + public Builder config(CORSConfiguration config) { + validateCors(config); + operations.add(args -> args.config = config); + return this; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SetBucketCorsArgs)) return false; + if (!super.equals(o)) return false; + SetBucketCorsArgs that = (SetBucketCorsArgs) o; + return Objects.equals(config, that.config); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), config); + } +} diff --git a/api/src/main/java/io/minio/messages/AccessControlList.java b/api/src/main/java/io/minio/messages/AccessControlList.java index 754990f8e..fff7fc582 100644 --- a/api/src/main/java/io/minio/messages/AccessControlList.java +++ b/api/src/main/java/io/minio/messages/AccessControlList.java @@ -30,11 +30,16 @@ public class AccessControlList { @ElementList(name = "Grant", inline = true) private List grants; - public AccessControlList(@Nonnull List grants) { + public AccessControlList( + @Nonnull @ElementList(name = "Grant", inline = true) List grants) { Objects.requireNonNull(grants, "Grants must not be null"); if (grants.size() == 0) { throw new IllegalArgumentException("Grants must not be empty"); } this.grants = Utils.unmodifiableList(grants); } + + public List grants() { + return Utils.unmodifiableList(grants); + } } diff --git a/api/src/main/java/io/minio/messages/AccessControlPolicy.java b/api/src/main/java/io/minio/messages/AccessControlPolicy.java new file mode 100644 index 000000000..f9b95921e --- /dev/null +++ b/api/src/main/java/io/minio/messages/AccessControlPolicy.java @@ -0,0 +1,113 @@ +/* + * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2025 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.messages; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import java.util.List; +import org.simpleframework.xml.Element; +import org.simpleframework.xml.Root; + +/** + * Object representation of response XML of GetObjectAcl + * API. + */ +@Root(name = "AccessControlPolicy", strict = false) +@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") +public class AccessControlPolicy { + @Element(name = "Owner", required = false) + private Owner owner; + + @Element(name = "AccessControlList", required = false) + private AccessControlList accessControlList; + + public AccessControlPolicy( + @Element(name = "Owner", required = false) Owner owner, + @Element(name = "AccessControlList", required = false) AccessControlList accessControlList) { + this.owner = owner; + this.accessControlList = accessControlList; + } + + public Owner owner() { + return owner; + } + + public AccessControlList accessControlList() { + return accessControlList; + } + + public String cannedAcl() { + if (accessControlList == null) return ""; + + List grants = accessControlList.grants(); + int size = grants.size(); + + if (size < 1 || size > 3) return ""; + + for (Grant grant : grants) { + if (grant == null) continue; + + String uri = grant.granteeUri(); + if (grant.permission() == Permission.FULL_CONTROL && size == 1 && "".equals(uri)) { + return "private"; + } else if (grant.permission() == Permission.READ && size == 2) { + if ("http://acs.amazonaws.com/groups/global/AuthenticatedUsers".equals(uri)) { + return "authenticated-read"; + } + if ("http://acs.amazonaws.com/groups/global/AllUsers".equals(uri)) return "public-read"; + if (owner.id() != null + && grant.granteeId() != null + && owner.id().equals(grant.granteeId())) { + return "bucket-owner-read"; + } + } else if (grant.permission() == Permission.WRITE + && size == 3 + && "http://acs.amazonaws.com/groups/global/AllUsers".equals(uri)) { + return "public-read-write"; + } + } + + return ""; + } + + public Multimap grantAcl() { + Multimap map = null; + + if (accessControlList != null) { + map = HashMultimap.create(); + for (Grant grant : accessControlList.grants()) { + if (grant == null) continue; + + String value = "id=" + grant.granteeId(); + if (grant.permission() == Permission.READ) { + map.put("X-Amz-Grant-Read", value); + } else if (grant.permission() == Permission.WRITE) { + map.put("X-Amz-Grant-Write", value); + } else if (grant.permission() == Permission.READ_ACP) { + map.put("X-Amz-Grant-Read-Acp", value); + } else if (grant.permission() == Permission.WRITE_ACP) { + map.put("X-Amz-Grant-Write-Acp", value); + } else if (grant.permission() == Permission.FULL_CONTROL) { + map.put("X-Amz-Grant-Full-Control", value); + } + } + } + + return map; + } +} diff --git a/api/src/main/java/io/minio/messages/CORSConfiguration.java b/api/src/main/java/io/minio/messages/CORSConfiguration.java new file mode 100644 index 000000000..aedc3d253 --- /dev/null +++ b/api/src/main/java/io/minio/messages/CORSConfiguration.java @@ -0,0 +1,111 @@ +/* + * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2025 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.messages; + +import io.minio.Utils; +import java.util.List; +import javax.annotation.Nullable; +import org.simpleframework.xml.Element; +import org.simpleframework.xml.ElementList; +import org.simpleframework.xml.Namespace; +import org.simpleframework.xml.Root; + +/** + * Object representation of request/response XML of PutBucketCors + * API and GetBucketCors + * API. + */ +@Root(name = "CORSConfiguration", strict = false) +@Namespace(reference = "http://s3.amazonaws.com/doc/2006-03-01/") +public class CORSConfiguration { + @ElementList(name = "CORSRule", inline = true, required = false) + private List rules; + + public CORSConfiguration( + @Nullable @ElementList(name = "CORSRule", required = false) List rules) { + this.rules = rules; + } + + public List rules() { + return Utils.unmodifiableList(rules); + } + + public static class CORSRule { + @ElementList(entry = "AllowedHeader", inline = true, required = false) + private List allowedHeaders; + + @ElementList(entry = "AllowedMethod", inline = true, required = false) + private List allowedMethods; + + @ElementList(entry = "AllowedOrigin", inline = true, required = false) + private List allowedOrigins; + + @ElementList(entry = "ExposeHeader", inline = true, required = false) + private List exposeHeaders; + + @Element(name = "ID", required = false) + private String id; + + @Element(name = "MaxAgeSeconds", required = false) + private Integer maxAgeSeconds; + + public CORSRule( + @Nullable @ElementList(entry = "AllowedHeader", inline = true, required = false) + List allowedHeaders, + @Nullable @ElementList(entry = "AllowedMethod", inline = true, required = false) + List allowedMethods, + @Nullable @ElementList(entry = "AllowedOrigin", inline = true, required = false) + List allowedOrigins, + @Nullable @ElementList(entry = "ExposeHeader", inline = true, required = false) + List exposeHeaders, + @Nullable @Element(name = "ID", required = false) String id, + @Nullable @Element(name = "MaxAgeSeconds", required = false) Integer maxAgeSeconds) { + this.allowedHeaders = allowedHeaders; + this.allowedMethods = allowedMethods; + this.allowedOrigins = allowedOrigins; + this.exposeHeaders = exposeHeaders; + this.id = id; + this.maxAgeSeconds = maxAgeSeconds; + } + + public List allowedHeaders() { + return Utils.unmodifiableList(allowedHeaders); + } + + public List allowedMethods() { + return Utils.unmodifiableList(allowedMethods); + } + + public List allowedOrigins() { + return Utils.unmodifiableList(allowedOrigins); + } + + public List exposeHeaders() { + return Utils.unmodifiableList(exposeHeaders); + } + + public String id() { + return id; + } + + public Integer maxAgeSeconds() { + return maxAgeSeconds; + } + } +} diff --git a/api/src/main/java/io/minio/messages/Checksum.java b/api/src/main/java/io/minio/messages/Checksum.java new file mode 100644 index 000000000..2263fa138 --- /dev/null +++ b/api/src/main/java/io/minio/messages/Checksum.java @@ -0,0 +1,88 @@ +/* + * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2025 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.messages; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import java.util.Locale; +import org.simpleframework.xml.Element; +import org.simpleframework.xml.Root; + +/** Helper class for. */ +@Root(name = "Checksum", strict = false) +public class Checksum { + @Element(name = "ChecksumCRC32", required = false) + private String checksumCRC32; + + @Element(name = "ChecksumCRC32C", required = false) + private String checksumCRC32C; + + @Element(name = "ChecksumCRC64NVME", required = false) + private String checksumCRC64NVME; + + @Element(name = "ChecksumSHA1", required = false) + private String checksumSHA1; + + @Element(name = "ChecksumSHA256", required = false) + private String checksumSHA256; + + @Element(name = "ChecksumType", required = false) + private String checksumType; + + public Checksum() {} + + public String checksumCRC32() { + return checksumCRC32; + } + + public String checksumCRC32C() { + return checksumCRC32C; + } + + public String checksumCRC64NVME() { + return checksumCRC64NVME; + } + + public String checksumSHA1() { + return checksumSHA1; + } + + public String checksumSHA256() { + return checksumSHA256; + } + + public String checksumType() { + return checksumType; + } + + private void addHeader(Multimap map, String algorithm, String value) { + if (value != null || !value.isEmpty()) { + map.put("x-amz-checksum-algorithm", algorithm); + map.put("x-amz-checksum-algorithm-" + algorithm.toLowerCase(Locale.US), value); + } + } + + public Multimap headers() { + Multimap map = HashMultimap.create(); + addHeader(map, "CRC32", checksumCRC32); + addHeader(map, "CRC32C", checksumCRC32C); + addHeader(map, "CRC64NVME", checksumCRC64NVME); + addHeader(map, "SHA1", checksumSHA1); + addHeader(map, "SHA256", checksumSHA256); + return map; + } +} diff --git a/api/src/main/java/io/minio/messages/GetObjectAttributesOutput.java b/api/src/main/java/io/minio/messages/GetObjectAttributesOutput.java new file mode 100644 index 000000000..f1b259763 --- /dev/null +++ b/api/src/main/java/io/minio/messages/GetObjectAttributesOutput.java @@ -0,0 +1,146 @@ +/* + * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2025 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.messages; + +import io.minio.Utils; +import java.time.ZonedDateTime; +import java.util.List; +import org.simpleframework.xml.Element; +import org.simpleframework.xml.ElementList; +import org.simpleframework.xml.Namespace; +import org.simpleframework.xml.Root; + +/** + * Object representation of response XML of GetObjectAttributes + * API. + */ +@Root(name = "GetObjectAttributesOutput", strict = false) +@Namespace(reference = "http://s3.amazonaws.com/doc/2006-03-01/") +public class GetObjectAttributesOutput { + @Element(name = "ETag", required = false) + private String etag; + + @Element(name = "Checksum", required = false) + private Checksum checksum; + + @Element(name = "ObjectParts", required = false) + private ObjectParts objectParts; + + @Element(name = "StorageClass", required = false) + private String storageClass; + + @Element(name = "ObjectSize", required = false) + private Long objectSize; + + private Boolean deleteMarker; + private ZonedDateTime lastModified; + private String versionId; + + public GetObjectAttributesOutput() {} + + public void setDeleteMarker(boolean deleteMarker) { + this.deleteMarker = deleteMarker; + } + + public void setLastModified(ZonedDateTime lastModified) { + this.lastModified = lastModified; + } + + public void setVersionId(String versionId) { + this.versionId = versionId; + } + + public Boolean deleteMarker() { + return deleteMarker; + } + + public ZonedDateTime lastModified() { + return lastModified; + } + + public String versionId() { + return versionId; + } + + public String etag() { + return etag; + } + + public Checksum checksum() { + return checksum; + } + + public ObjectParts objectParts() { + return objectParts; + } + + public String storageClass() { + return storageClass; + } + + public Long objectSize() { + return objectSize; + } + + @Root(name = "ObjectParts", strict = false) + public static class ObjectParts { + @Element(name = "IsTruncated", required = false) + private boolean isTruncated; + + @Element(name = "MaxParts", required = false) + private Integer maxParts; + + @Element(name = "NextPartNumberMarker", required = false) + private Integer nextPartNumberMarker; + + @Element(name = "PartNumberMarker", required = false) + private Integer partNumberMarker; + + @ElementList(name = "Part", inline = true, required = false) + private List parts; + + @Element(name = "PartsCount", required = false) + private Integer partsCount; + + public ObjectParts() {} + + public boolean isTruncated() { + return isTruncated; + } + + public Integer maxParts() { + return maxParts; + } + + public Integer nextPartNumberMarker() { + return nextPartNumberMarker; + } + + public Integer partNumberMarker() { + return partNumberMarker; + } + + public List parts() { + return Utils.unmodifiableList(parts); + } + + public Integer partsCount() { + return partsCount; + } + } +} diff --git a/api/src/main/java/io/minio/messages/Grant.java b/api/src/main/java/io/minio/messages/Grant.java index 410f2a095..940240447 100644 --- a/api/src/main/java/io/minio/messages/Grant.java +++ b/api/src/main/java/io/minio/messages/Grant.java @@ -30,11 +30,29 @@ public class Grant { @Element(name = "Permission", required = false) private Permission permission; - public Grant(@Nullable Grantee grantee, @Nullable Permission permission) { + public Grant( + @Nullable @Element(name = "Grantee", required = false) Grantee grantee, + @Nullable @Element(name = "Permission", required = false) Permission permission) { if (grantee == null && permission == null) { throw new IllegalArgumentException("Either Grantee or Permission must be provided"); } this.grantee = grantee; this.permission = permission; } + + public Grantee grantee() { + return grantee; + } + + public Permission permission() { + return permission; + } + + public String granteeUri() { + return grantee == null ? null : grantee.uri(); + } + + public String granteeId() { + return grantee == null ? null : grantee.id(); + } } diff --git a/api/src/main/java/io/minio/messages/Grantee.java b/api/src/main/java/io/minio/messages/Grantee.java index 4d6fb9cab..42b17107d 100644 --- a/api/src/main/java/io/minio/messages/Grantee.java +++ b/api/src/main/java/io/minio/messages/Grantee.java @@ -19,13 +19,19 @@ import java.util.Objects; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import org.simpleframework.xml.Attribute; import org.simpleframework.xml.Element; +import org.simpleframework.xml.Namespace; import org.simpleframework.xml.Root; /** Helper class to denote for the person being granted permissions of {@link Grant}. */ @Root(name = "Grantee") +@Namespace(prefix = "xsi", reference = "http://www.w3.org/2001/XMLSchema-instance") @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") public class Grantee { + @Attribute(name = "type") + private String xsiType; + @Element(name = "DisplayName", required = false) private String displayName; @@ -53,4 +59,35 @@ public Grantee( this.id = id; this.uri = uri; } + + public Grantee( + @Nonnull @Attribute(name = "type") String xsiType, + @Nonnull @Element(name = "Type") GranteeType type, + @Nullable @Element(name = "DisplayName", required = false) String displayName, + @Nullable @Element(name = "EmailAddress", required = false) String emailAddress, + @Nullable @Element(name = "ID", required = false) String id, + @Nullable @Element(name = "URI", required = false) String uri) { + this(type, displayName, emailAddress, id, uri); + this.xsiType = xsiType; + } + + public String displayName() { + return displayName; + } + + public String emailAddress() { + return emailAddress; + } + + public String id() { + return id; + } + + public GranteeType type() { + return type; + } + + public String uri() { + return uri; + } } diff --git a/api/src/main/java/io/minio/messages/Part.java b/api/src/main/java/io/minio/messages/Part.java index e3ab860ef..d8aac178e 100644 --- a/api/src/main/java/io/minio/messages/Part.java +++ b/api/src/main/java/io/minio/messages/Part.java @@ -26,10 +26,10 @@ */ @Root(name = "Part", strict = false) public class Part { - @Element(name = "PartNumber") + @Element(name = "PartNumber", required = false) private int partNumber; - @Element(name = "ETag") + @Element(name = "ETag", required = false) private String etag; @Element(name = "LastModified", required = false) diff --git a/docs/API.md b/docs/API.md index d62d63bc5..334d3c385 100644 --- a/docs/API.md +++ b/docs/API.md @@ -25,29 +25,32 @@ MinioClient minioClient = | Bucket operations | Object operations | |-------------------------------------------------------------------|---------------------------------------------------------| | [`bucketExists`](#bucketExists) | [`composeObject`](#composeObject) | -| [`deleteBucketEncryption`](#deleteBucketEncryption) | [`copyObject`](#copyObject) | -| [`deleteBucketLifecycle`](#deleteBucketLifecycle) | [`deleteObjectTags`](#deleteObjectTags) | -| [`deleteBucketNotification`](#deleteBucketNotification) | [`disableObjectLegalHold`](#disableObjectLegalHold) | -| [`deleteBucketPolicy`](#deleteBucketPolicy) | [`downloadObject`](#downloadObject) | -| [`deleteBucketReplication`](#deleteBucketReplication) | [`enableObjectLegalHold`](#enableObjectLegalHold) | -| [`deleteBucketTags`](#deleteBucketTags) | [`getObject`](#getObject) | -| [`deleteObjectLockConfiguration`](#deleteObjectLockConfiguration) | [`getObjectRetention`](#getObjectRetention) | +| [`deleteBucketCors`](#deleteBucketCors) | [`copyObject`](#copyObject) | +| [`deleteBucketEncryption`](#deleteBucketEncryption) | [`deleteObjectTags`](#deleteObjectTags) | +| [`deleteBucketLifecycle`](#deleteBucketLifecycle) | [`disableObjectLegalHold`](#disableObjectLegalHold) | +| [`deleteBucketNotification`](#deleteBucketNotification) | [`downloadObject`](#downloadObject) | +| [`deleteBucketPolicy`](#deleteBucketPolicy) | [`enableObjectLegalHold`](#enableObjectLegalHold) | +| [`deleteBucketReplication`](#deleteBucketReplication) | [`getObject`](#getObject) | +| [`deleteBucketTags`](#deleteBucketTags) | [`getObjectAcl`](#getObjectAcl) | +| [`deleteObjectLockConfiguration`](#deleteObjectLockConfiguration) | [`getObjectAttributes`](#getObjectAttributes) | +| [`getBucketCors`](#getBucketCors) | [`getObjectRetention`](#getObjectRetention) | | [`getBucketEncryption`](#getBucketEncryption) | [`getObjectTags`](#getObjectTags) | | [`getBucketLifecycle`](#getBucketLifecycle) | [`getPresignedObjectUrl`](#getPresignedObjectUrl) | | [`getBucketNotification`](#getBucketNotification) | [`getPresignedPostFormData`](#getPresignedPostFormData) | | [`getBucketPolicy`](#getBucketPolicy) | [`isObjectLegalHoldEnabled`](#isObjectLegalHoldEnabled) | | [`getBucketReplication`](#getBucketReplication) | [`listObjects`](#listObjects) | -| [`getBucketTags`](#getBucketTags) | [`putObject`](#putObject) | -| [`getBucketVersioning`](#getBucketVersioning) | [`removeObject`](#removeObject) | -| [`getObjectLockConfiguration`](#getObjectLockConfiguration) | [`removeObjects`](#removeObjects) | -| [`listBuckets`](#listBuckets) | [`restoreObject`](#restoreObject) | -| [`listenBucketNotification`](#listenBucketNotification) | [`selectObjectContent`](#selectObjectContent) | -| [`makeBucket`](#makeBucket) | [`setObjectRetention`](#setObjectRetention) | -| [`removeBucket`](#removeBucket) | [`setObjectTags`](#setObjectTags) | -| [`setBucketEncryption`](#setBucketEncryption) | [`statObject`](#statObject) | -| [`setBucketLifecycle`](#setBucketLifecycle) | [`uploadObject`](#uploadObject) | -| [`setBucketNotification`](#setBucketNotification) | [`uploadSnowballObjects`](#uploadSnowballObjects) | -| [`setBucketPolicy`](#setBucketPolicy) | | +| [`getBucketTags`](#getBucketTags) | [`promptObject`](#promptObject) | +| [`getBucketVersioning`](#getBucketVersioning) | [`putObject`](#putObject) | +| [`getObjectLockConfiguration`](#getObjectLockConfiguration) | [`putObjectFanOut`](#putObjectFanOut) | +| [`listBuckets`](#listBuckets) | [`removeObject`](#removeObject) | +| [`listenBucketNotification`](#listenBucketNotification) | [`removeObjects`](#removeObjects) | +| [`makeBucket`](#makeBucket) | [`restoreObject`](#restoreObject) | +| [`removeBucket`](#removeBucket) | [`selectObjectContent`](#selectObjectContent) | +| [`setBucketCors`](#setBucketCors) | [`setObjectRetention`](#setObjectRetention) | +| [`setBucketEncryption`](#setBucketEncryption) | [`setObjectTags`](#setObjectTags) | +| [`setBucketLifecycle`](#setBucketLifecycle) | [`statObject`](#statObject) | +| [`setBucketNotification`](#setBucketNotification) | [`uploadObject`](#uploadObject) | +| [`setBucketPolicy`](#setBucketPolicy) | [`uploadSnowballObjects`](#uploadSnowballObjects) | | [`setBucketReplication`](#setBucketReplication) | | | [`setBucketTags`](#setBucketTags) | | | [`setBucketVersioning`](#setBucketVersioning) | | @@ -253,6 +256,22 @@ if (found) { } ``` + +### deleteBucketCors(DeleteBucketCorsArgs args) +`private void deleteBucketCors(DeleteBucketCorsArgs args)` _[[Javadoc]](http://minio.github.io/minio-java/io/minio/MinioClient.html#deleteBucketCors-io.minio.DeleteBucketCorsArgs-)_ + +Deletes CORS configuration of a bucket. + +__Parameters__ +| Parameter | Type | Description | +|:----------|:-------------------------|:------------| +| ``args`` | _[DeleteBucketCorsArgs]_ | Arguments. | + +__Example__ +```java +minioClient.deleteBucketCors(DeleteBucketCorsArgs.builder().bucket("my-bucketname").build()); +``` + ### deleteBucketEncryption(DeleteBucketEncryptionArgs args) `private void deleteBucketEncryption(DeleteBucketEncryptionArgs args)` _[[Javadoc]](http://minio.github.io/minio-java/io/minio/MinioClient.html#deleteBucketEncryption-io.minio.DeleteBucketEncryptionArgs-)_ @@ -370,6 +389,27 @@ minioClient.deleteObjectLockConfiguration( DeleteObjectLockConfigurationArgs.builder().bucket("my-bucketname").build()); ``` + +### getBucketCors(GetBucketCorsArgs args) +`public Tags getBucketCors(GetBucketCorsArgs args)` _[[Javadoc]](http://minio.github.io/minio-java/io/minio/MinioClient.htmlgetBucketCors-io.minio.GetBucketCorsArgs-)_ + +Gets CORS configuration of a bucket. + +__Parameters__ +| Parameter | Type | Description | +|:----------|:----------------------|:------------| +| ``args`` | _[GetBucketCorsArgs]_ | Arguments. | + + +| Returns | +|:--------------------------------------------| +| _[CORSConfiguration]_ - CORS configuration. | + +__Example__ +```java +CORSConfiguration config = minioClient.getBucketCors(GetBucketCorsArgs.builder().bucket("my-bucketname").build()); +``` + ### getBucketEncryption(GetBucketEncryptionArgs args) `public SseConfiguration getBucketEncryption(GetBucketEncryptionArgs args)` _[[Javadoc]](http://minio.github.io/minio-java/io/minio/MinioClient.html#getBucketEncryption-io.minio.GetBucketEncryptionArgs-)_ @@ -727,6 +767,46 @@ __Example__ minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build()); ``` + +### setBucketCors(SetBucketCorsArgs args) +`public void setBucketCors(SetBucketCorsArgs args)` _[[Javadoc]](http://minio.github.io/minio-java/io/minio/MinioClient.html#setBucketCors-io.minio.SetBucketCorsArgs-)_ + +Sets CORS configuration to a bucket. + +__Parameters__ + +| Parameter | Type | Description | +|:----------|:----------------------|:------------| +| ``args`` | _[SetBucketCorsArgs]_ | Arguments. | + +__Example__ +```java +CORSConfiguration config = + new CORSConfiguration( + Arrays.asList( + new CORSConfiguration.CORSRule[] { + // Rule 1 + new CORSConfiguration.CORSRule( + Arrays.asList(new String[] {"*"}), // Allowed headers + Arrays.asList(new String[] {"PUT", "POST", "DELETE"}), // Allowed methods + Arrays.asList(new String[] {"http://www.example.com"}), // Allowed origins + Arrays.asList( + new String[] {"x-amz-server-side-encryption"}), // Expose headers + null, // ID + 3000), // Maximum age seconds + // Rule 2 + new CORSConfiguration.CORSRule( + null, // Allowed headers + Arrays.asList(new String[] {"GET"}), // Allowed methods + Arrays.asList(new String[] {"*"}), // Allowed origins + null, // Expose headers + null, // ID + null // Maximum age seconds + ) + })); +minioClient.setBucketCors(SetBucketCorsArgs.builder().bucket("my-bucketname").config(config).build()); +``` + ### setBucketEncryption(SetBucketEncryptionArgs args) `public void setBucketEncryption(SetBucketEncryptionArgs args)` _[[Javadoc]](http://minio.github.io/minio-java/io/minio/MinioClient.html#setBucketEncryption-io.minio.SetBucketEncryptionArgs-)_ @@ -1262,6 +1342,58 @@ try (InputStream stream = minioClient.getObject( } ``` + +### getObjectAcl(GetObjectAclArgs args) +`public Acl getObjectAcl(GetObjectAclArgs args)` _[[Javadoc]](http://minio.github.io/minio-java/io/minio/MinioClient.html#getObjectAcl-io.minio.GetObjectAclArgs-)_ + +Gets tags of an object. + +__Parameters__ +| Parameter | Type | Description | +|:----------|:---------------------|:------------| +| ``args`` | _[GetObjectAclArgs]_ | Arguments. | + + +| Returns | +|:-------------------------------------------------| +| _[AccessControlPolicy]_ - Access control policy. | + +__Example__ +```java +AccessControlPolicy policy = minioClient.getObjectAcl( + GetObjectAclArgs.builder().bucket("my-bucketname").object("my-objectname").build()); +``` + + +### getObjectAttributes(GetObjectAttributesArgs args) +`public GetObjectAttributesResponse getObjectAttributes(GetObjectAttributesArgs args)` _[[Javadoc]](http://minio.github.io/minio-java/io/minio/MinioClient.html#getObjectAttributes-io.minio.GetObjectAttributesArgs-)_ + +Gets tags of an object. + +__Parameters__ +| Parameter | Type | Description | +|:----------|:----------------------------|:------------| +| ``args`` | _[GetObjectAttributesArgs]_ | Arguments. | + + +| Returns | +|:-------------------------------------------| +| _[GetObjectAttributesResponse]_ - Respone. | + +__Example__ +```java +GetObjectAttributesResponse response = + minioClient.getObjectAttributes( + GetObjectAttributesArgs.builder() + .bucket("my-bucketname") + .object("my-objectname") + .objectAttributes( + new String[] { + "ETag", "Checksum", "ObjectParts", "StorageClass", "ObjectSize" + }) + .build()); +``` + ### downloadObject(DownloadObjectArgs args) `public void downloadObject(DownloadObjectArgs args)` _[[Javadoc]](http://minio.github.io/minio-java/io/minio/MinioClient.html#getObject-io.minio.DownloadObjectArgs-)_ @@ -1504,6 +1636,21 @@ if (response.isSuccessful()) { } ``` + +### promptObject(PromptObjectArgs args) +`public ObjectWriteResponse promptObject(PromptObjectArgs args)` _[[Javadoc]](http://minio.github.io/minio-java/io/minio/MinioClient.html#promptObject-io.minio.PromptObjectArgs-)_ + +Performs language model inference with the prompt and referenced object as context. + +__Parameters__ +| Parameter | Type | Description | +|:----------|:---------------------|:------------| +| ``args`` | _[PromptObjectArgs]_ | Arguments. | + +| Returns | +|:-------------------------------------| +| _[PromptObjectResponse]_ - response. | + ### putObject(PutObjectArgs args) `public ObjectWriteResponse putObject(PutObjectArgs args)` _[[Javadoc]](http://minio.github.io/minio-java/io/minio/MinioClient.html#putObject-io.minio.PutObjectArgs-)_ @@ -1561,6 +1708,40 @@ minioClient.putObject( .build()); ``` + +### putObjectFanOut(PutObjectFanOutArgs args) +`public PutObjectFanOutResponse putObjectFanOut(PutObjectFanOutArgs args)` _[[Javadoc]](http://minio.github.io/minio-java/io/minio/MinioClient.html#putObjectFanOut-io.minio.PutObjectFanOutArgs-)_ + +Uploads multiple objects with same content from single stream with optional metadata and tags. + +__Parameters__ +| Parameter | Type | Description | +|:----------|:------------------------|:------------| +| ``args`` | _[PutObjectFanOutArgs]_ | Arguments. | + + +| Returns | +|:----------------------------------------| +| _[PutObjectFanOutResponse]_ - response. | + +__Example__ +```java +Map map = new HashMap<>(); +map.put("Project", "Project One"); +map.put("User", "jsmith"); +PutObjectFanOutResponse response = + minioClient.putObjectFanOut( + PutObjectFanOutArgs.builder().bucket("my-bucketname").stream( + new ByteArrayInputStream("somedata".getBytes(StandardCharsets.UTF_8)), 8) + .entries( + Arrays.asList( + new PutObjectFanOutEntry[] { + PutObjectFanOutEntry.builder().key("fan-out.0").build(), + PutObjectFanOutEntry.builder().key("fan-out.1").tags(map).build() + })) + .build()); +``` + ### uploadObject(UploadObjectArgs args) `public void uploadObject(UploadObjectArgs args)` _[[Javadoc]](http://minio.github.io/minio-java/io/minio/MinioClient.html#uploadObject-io.minio.UploadObjectArgs-)_ @@ -1949,3 +2130,14 @@ ObjectStat objectStat = [GetBucketVersioningArgs]: http://minio.github.io/minio-java/io/minio/GetBucketVersioningArgs.html [SetBucketVersioningArgs]: http://minio.github.io/minio-java/io/minio/SetBucketVersioningArgs.html [RestoreObjectArgs]: http://minio.github.io/minio-java/io/minio/RestoreObjectArgs.html +[DeleteBucketCorsArgs]: http://minio.github.io/minio-java/io/minio/DeleteBucketCorsArgs.html +[GetBucketCorsArgs]: http://minio.github.io/minio-java/io/minio/GetBucketCorsArgs.html +[SetBucketCorsArgs]: http://minio.github.io/minio-java/io/minio/SetBucketCorsArgs.html +[GetObjectAclArgs]: http://minio.github.io/minio-java/io/minio/GetObjectAclArgs.html +[AccessControlPolicy]: http://minio.github.io/minio-java/io/minio/messages/AccessControlPolicy.html +[GetObjectAttributesArgs]: http://minio.github.io/minio-java/io/minio/GetObjectAttributesArgs.html +[GetObjectAttributesResponse]: http://minio.github.io/minio-java/io/minio/GetObjectAttributesResponse.html +[PutObjectFanOutArgs]: http://minio.github.io/minio-java/io/minio/PutObjectFanOutArgs.html +[PutObjectFanOutResponse]: http://minio.github.io/minio-java/io/minio/PutObjectFanOutResponse.html +[PromptObjectArgs]: http://minio.github.io/minio-java/io/minio/PromptObjectArgs.html +[PromptObjectResponse]: http://minio.github.io/minio-java/io/minio/PromptObjectResponse.html diff --git a/examples/DeleteBucketCors.java b/examples/DeleteBucketCors.java new file mode 100644 index 000000000..86990ab43 --- /dev/null +++ b/examples/DeleteBucketCors.java @@ -0,0 +1,49 @@ +/* + * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2025 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 + * + * 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. + */ + +import io.minio.DeleteBucketCorsArgs; +import io.minio.MinioClient; +import io.minio.errors.MinioException; +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +public class DeleteBucketCors { + /** MinioClient.deleteBucketCors() example. */ + public static void main(String[] args) + throws IOException, NoSuchAlgorithmException, InvalidKeyException { + try { + /* play.min.io for test and development. */ + MinioClient minioClient = + MinioClient.builder() + .endpoint("https://play.min.io") + .credentials("Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG") + .build(); + + /* Amazon S3: */ + // MinioClient minioClient = + // MinioClient.builder() + // .endpoint("https://s3.amazonaws.com") + // .credentials("YOUR-ACCESSKEY", "YOUR-SECRETACCESSKEY") + // .build(); + + minioClient.deleteBucketCors(DeleteBucketCorsArgs.builder().bucket("my-bucketname").build()); + System.out.println("Bucket CORS configuration deleted successfully"); + } catch (MinioException e) { + System.out.println("Error occurred: " + e); + } + } +} diff --git a/examples/GetBucketCors.java b/examples/GetBucketCors.java new file mode 100644 index 000000000..713f8fc07 --- /dev/null +++ b/examples/GetBucketCors.java @@ -0,0 +1,51 @@ +/* + * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2025 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 + * + * 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. + */ + +import io.minio.GetBucketCorsArgs; +import io.minio.MinioClient; +import io.minio.errors.MinioException; +import io.minio.messages.CORSConfiguration; +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +public class GetBucketCors { + /** MinioClient.getBucketCors() example. */ + public static void main(String[] args) + throws IOException, NoSuchAlgorithmException, InvalidKeyException { + try { + /* play.min.io for test and development. */ + MinioClient minioClient = + MinioClient.builder() + .endpoint("https://play.min.io") + .credentials("Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG") + .build(); + + /* Amazon S3: */ + // MinioClient minioClient = + // MinioClient.builder() + // .endpoint("https://s3.amazonaws.com") + // .credentials("YOUR-ACCESSKEY", "YOUR-SECRETACCESSKEY") + // .build(); + + CORSConfiguration config = + minioClient.getBucketCors(GetBucketCorsArgs.builder().bucket("my-bucketname").build()); + System.out.println("Bucket CORS configuration rules: " + config.rules()); + } catch (MinioException e) { + System.out.println("Error occurred: " + e); + } + } +} diff --git a/examples/GetObjectAcl.java b/examples/GetObjectAcl.java new file mode 100644 index 000000000..97a481649 --- /dev/null +++ b/examples/GetObjectAcl.java @@ -0,0 +1,52 @@ +/* + * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2025 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 + * + * 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. + */ + +import io.minio.GetObjectAclArgs; +import io.minio.MinioClient; +import io.minio.errors.MinioException; +import io.minio.messages.AccessControlPolicy; +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +public class GetObjectAcl { + /** MinioClient.getObjectAcl() example. */ + public static void main(String[] args) + throws IOException, NoSuchAlgorithmException, InvalidKeyException { + try { + /* play.min.io for test and development. */ + MinioClient minioClient = + MinioClient.builder() + .endpoint("https://play.min.io") + .credentials("Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG") + .build(); + + /* Amazon S3: */ + // MinioClient minioClient = + // MinioClient.builder() + // .endpoint("https://s3.amazonaws.com") + // .credentials("YOUR-ACCESSKEY", "YOUR-SECRETACCESSKEY") + // .build(); + + AccessControlPolicy policy = + minioClient.getObjectAcl( + GetObjectAclArgs.builder().bucket("my-bucketname").object("my-objectname").build()); + System.out.println("Access control policy: " + policy); + } catch (MinioException e) { + System.out.println("Error occurred: " + e); + } + } +} diff --git a/examples/GetObjectAttributes.java b/examples/GetObjectAttributes.java new file mode 100644 index 000000000..b2a03e426 --- /dev/null +++ b/examples/GetObjectAttributes.java @@ -0,0 +1,59 @@ +/* + * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2025 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 + * + * 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. + */ + +import io.minio.GetObjectAttributesArgs; +import io.minio.GetObjectAttributesResponse; +import io.minio.MinioClient; +import io.minio.errors.MinioException; +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +public class GetObjectAttributes { + /** MinioClient.getObjectAttributes() example. */ + public static void main(String[] args) + throws IOException, NoSuchAlgorithmException, InvalidKeyException { + try { + /* play.min.io for test and development. */ + MinioClient minioClient = + MinioClient.builder() + .endpoint("https://play.min.io") + .credentials("Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG") + .build(); + + /* Amazon S3: */ + // MinioClient minioClient = + // MinioClient.builder() + // .endpoint("https://s3.amazonaws.com") + // .credentials("YOUR-ACCESSKEY", "YOUR-SECRETACCESSKEY") + // .build(); + + GetObjectAttributesResponse response = + minioClient.getObjectAttributes( + GetObjectAttributesArgs.builder() + .bucket("my-bucketname") + .object("my-objectname") + .objectAttributes( + new String[] { + "ETag", "Checksum", "ObjectParts", "StorageClass", "ObjectSize" + }) + .build()); + System.out.println("Response: " + response); + } catch (MinioException e) { + System.out.println("Error occurred: " + e); + } + } +} diff --git a/examples/GetObjectResume.java b/examples/GetObjectResume.java new file mode 100644 index 000000000..ff9984cc6 --- /dev/null +++ b/examples/GetObjectResume.java @@ -0,0 +1,55 @@ +import com.google.common.io.ByteStreams; +import io.minio.GetObjectArgs; +import io.minio.MinioClient; +import io.minio.StatObjectArgs; +import io.minio.StatObjectResponse; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; + +public class GetObjectResume { + public static void main(String[] args) throws Exception { + MinioClient minioClient = + MinioClient.builder() + .endpoint("https://play.min.io") + .credentials("Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG") + .build(); + + String filename = "my-objectname"; + Path path = Paths.get(filename); + long fileSize = 0; + if (Files.exists(path)) fileSize = Files.size(path); + + StatObjectResponse stat = + minioClient.statObject( + StatObjectArgs.builder().bucket("my-bucketname").object("my-objectname").build()); + + if (fileSize == stat.size()) { + // Already fully downloaded. + return; + } + + if (fileSize > stat.size()) { + throw new Exception("stored file size is greater than object size"); + } + + InputStream stream = + minioClient.getObject( + GetObjectArgs.builder() + .bucket("my-bucketname") + .object("my-objectname") + .offset(fileSize) + .build()); + + try (OutputStream os = + Files.newOutputStream( + path, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.APPEND)) { + ByteStreams.copy(stream, os); + } finally { + stream.close(); + } + } +} diff --git a/examples/PutObjectFanOut.java b/examples/PutObjectFanOut.java new file mode 100644 index 000000000..ca9c2dfea --- /dev/null +++ b/examples/PutObjectFanOut.java @@ -0,0 +1,69 @@ +/* + * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2025 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 + * + * 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. + */ + +import io.minio.MinioClient; +import io.minio.PutObjectFanOutArgs; +import io.minio.PutObjectFanOutEntry; +import io.minio.PutObjectFanOutResponse; +import io.minio.errors.MinioException; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +public class PutObjectFanOut { + /** MinioClient.putObject() example. */ + public static void main(String[] args) + throws IOException, NoSuchAlgorithmException, InvalidKeyException { + try { + /* play.min.io for test and development. */ + MinioClient minioClient = + MinioClient.builder() + .endpoint("https://play.min.io") + .credentials("Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG") + .build(); + + /* Amazon S3: */ + // MinioClient minioClient = + // MinioClient.builder() + // .endpoint("https://s3.amazonaws.com") + // .credentials("YOUR-ACCESSKEY", "YOUR-SECRETACCESSKEY") + // .build(); + + Map map = new HashMap<>(); + map.put("Project", "Project One"); + map.put("User", "jsmith"); + PutObjectFanOutResponse response = + minioClient.putObjectFanOut( + PutObjectFanOutArgs.builder().bucket("my-bucketname").stream( + new ByteArrayInputStream("somedata".getBytes(StandardCharsets.UTF_8)), 8) + .entries( + Arrays.asList( + new PutObjectFanOutEntry[] { + PutObjectFanOutEntry.builder().key("fan-out.0").build(), + PutObjectFanOutEntry.builder().key("fan-out.1").tags(map).build() + })) + .build()); + System.out.println("response: " + response); + } catch (MinioException e) { + System.out.println("Error occurred: " + e); + } + } +} diff --git a/examples/SetBucketCors.java b/examples/SetBucketCors.java new file mode 100644 index 000000000..37ec1fcfc --- /dev/null +++ b/examples/SetBucketCors.java @@ -0,0 +1,75 @@ +/* + * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2020 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 + * + * 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. + */ + +import io.minio.MinioClient; +import io.minio.SetBucketCorsArgs; +import io.minio.errors.MinioException; +import io.minio.messages.CORSConfiguration; +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +public class SetBucketCors { + /** MinioClient.setBucketCors() example. */ + public static void main(String[] args) + throws IOException, NoSuchAlgorithmException, InvalidKeyException { + try { + /* play.min.io for test and development. */ + MinioClient minioClient = + MinioClient.builder() + .endpoint("https://play.min.io") + .credentials("Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG") + .build(); + + /* Amazon S3: */ + // MinioClient minioClient = + // MinioClient.builder() + // .endpoint("https://s3.amazonaws.com") + // .credentials("YOUR-ACCESSKEY", "YOUR-SECRETACCESSKEY") + // .build(); + + CORSConfiguration config = + new CORSConfiguration( + Arrays.asList( + new CORSConfiguration.CORSRule[] { + // Rule 1 + new CORSConfiguration.CORSRule( + Arrays.asList(new String[] {"*"}), // Allowed headers + Arrays.asList(new String[] {"PUT", "POST", "DELETE"}), // Allowed methods + Arrays.asList(new String[] {"http://www.example.com"}), // Allowed origins + Arrays.asList( + new String[] {"x-amz-server-side-encryption"}), // Expose headers + null, // ID + 3000), // Maximum age seconds + // Rule 2 + new CORSConfiguration.CORSRule( + null, // Allowed headers + Arrays.asList(new String[] {"GET"}), // Allowed methods + Arrays.asList(new String[] {"*"}), // Allowed origins + null, // Expose headers + null, // ID + null // Maximum age seconds + ) + })); + + minioClient.setBucketCors( + SetBucketCorsArgs.builder().bucket("my-bucketname").config(config).build()); + } catch (MinioException e) { + System.out.println("Error occurred: " + e); + } + } +} diff --git a/functional/FunctionalTest.java b/functional/FunctionalTest.java index b4ca17466..3f2234333 100644 --- a/functional/FunctionalTest.java +++ b/functional/FunctionalTest.java @@ -26,6 +26,7 @@ import io.minio.ComposeSource; import io.minio.CopyObjectArgs; import io.minio.CopySource; +import io.minio.DeleteBucketCorsArgs; import io.minio.DeleteBucketEncryptionArgs; import io.minio.DeleteBucketLifecycleArgs; import io.minio.DeleteBucketNotificationArgs; @@ -38,6 +39,7 @@ import io.minio.DisableObjectLegalHoldArgs; import io.minio.DownloadObjectArgs; import io.minio.EnableObjectLegalHoldArgs; +import io.minio.GetBucketCorsArgs; import io.minio.GetBucketEncryptionArgs; import io.minio.GetBucketLifecycleArgs; import io.minio.GetBucketNotificationArgs; @@ -45,7 +47,10 @@ import io.minio.GetBucketReplicationArgs; import io.minio.GetBucketTagsArgs; import io.minio.GetBucketVersioningArgs; +import io.minio.GetObjectAclArgs; import io.minio.GetObjectArgs; +import io.minio.GetObjectAttributesArgs; +import io.minio.GetObjectAttributesResponse; import io.minio.GetObjectLockConfigurationArgs; import io.minio.GetObjectRetentionArgs; import io.minio.GetObjectTagsArgs; @@ -59,6 +64,8 @@ import io.minio.ObjectWriteResponse; import io.minio.PostPolicy; import io.minio.PutObjectArgs; +import io.minio.PutObjectFanOutArgs; +import io.minio.PutObjectFanOutEntry; import io.minio.RemoveBucketArgs; import io.minio.RemoveObjectArgs; import io.minio.RemoveObjectsArgs; @@ -69,6 +76,7 @@ import io.minio.ServerSideEncryptionCustomerKey; import io.minio.ServerSideEncryptionKms; import io.minio.ServerSideEncryptionS3; +import io.minio.SetBucketCorsArgs; import io.minio.SetBucketEncryptionArgs; import io.minio.SetBucketLifecycleArgs; import io.minio.SetBucketNotificationArgs; @@ -90,14 +98,17 @@ import io.minio.errors.ErrorResponseException; import io.minio.http.HttpUtils; import io.minio.http.Method; +import io.minio.messages.AccessControlPolicy; import io.minio.messages.AndOperator; import io.minio.messages.Bucket; +import io.minio.messages.CORSConfiguration; import io.minio.messages.DeleteMarkerReplication; import io.minio.messages.DeleteObject; import io.minio.messages.Event; import io.minio.messages.EventType; import io.minio.messages.Expiration; import io.minio.messages.FileHeaderInfo; +import io.minio.messages.GranteeType; import io.minio.messages.InputSerialization; import io.minio.messages.LifecycleConfiguration; import io.minio.messages.LifecycleRule; @@ -105,6 +116,7 @@ import io.minio.messages.NotificationRecords; import io.minio.messages.ObjectLockConfiguration; import io.minio.messages.OutputSerialization; +import io.minio.messages.Permission; import io.minio.messages.QueueConfiguration; import io.minio.messages.QuoteFields; import io.minio.messages.ReplicationConfiguration; @@ -3316,6 +3328,72 @@ public static void deleteBucketEncryption() throws Exception { } } + public static void testBucketCors(String methodName, boolean getTest, boolean deleteTest) + throws Exception { + if (!mintEnv) { + System.out.println(methodName); + } + + long startTime = System.currentTimeMillis(); + String bucketName = getRandomName(); + try { + client.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build()); + try { + CORSConfiguration expectedConfig = + new CORSConfiguration( + Arrays.asList( + new CORSConfiguration.CORSRule[] { + // Rule 1 + new CORSConfiguration.CORSRule( + Arrays.asList(new String[] {"*"}), // Allowed headers + Arrays.asList(new String[] {"PUT", "POST", "DELETE"}), // Allowed methods + Arrays.asList(new String[] {"http://www.example.com"}), // Allowed origins + Arrays.asList( + new String[] {"x-amz-server-side-encryption"}), // Expose headers + null, // ID + 3000), // Maximum age seconds + // Rule 2 + new CORSConfiguration.CORSRule( + null, // Allowed headers + Arrays.asList(new String[] {"GET"}), // Allowed methods + Arrays.asList(new String[] {"*"}), // Allowed origins + null, // Expose headers + null, // ID + null // Maximum age seconds + ) + })); + client.setBucketCors( + SetBucketCorsArgs.builder().bucket(bucketName).config(expectedConfig).build()); + if (getTest) { + CORSConfiguration config = + client.getBucketCors(GetBucketCorsArgs.builder().bucket(bucketName).build()); + Assert.assertEquals( + "cors: expected: " + expectedConfig + ", got: " + config, expectedConfig, config); + } + if (deleteTest) { + client.deleteBucketCors(DeleteBucketCorsArgs.builder().bucket(bucketName).build()); + } + mintSuccessLog(methodName, null, startTime); + } finally { + client.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build()); + } + } catch (Exception e) { + handleException(methodName, null, startTime, e); + } + } + + public static void setBucketCors() throws Exception { + testBucketCors("setBucketCors()", false, false); + } + + public static void getBucketCors() throws Exception { + testBucketCors("getBucketCors()", true, false); + } + + public static void deleteBucketCors() throws Exception { + testBucketCors("deleteBucketCors()", false, true); + } + public static void setBucketTags() throws Exception { String methodName = "setBucketTags()"; if (!mintEnv) { @@ -3503,6 +3581,94 @@ public static void deleteObjectTags() throws Exception { } } + public static void getObjectAcl() throws Exception { + String methodName = "getObjectAcl()"; + if (!mintEnv) { + System.out.println(methodName); + } + + long startTime = System.currentTimeMillis(); + String objectName = getRandomName(); + try { + try { + client.putObject( + PutObjectArgs.builder().bucket(bucketName).object(objectName).stream( + new ContentInputStream(1 * KB), 1 * KB, -1) + .build()); + AccessControlPolicy policy = + client.getObjectAcl( + GetObjectAclArgs.builder().bucket(bucketName).object(objectName).build()); + Assert.assertEquals( + "granteeType: expected: " + + GranteeType.CANONICAL_USER + + ", got: " + + policy.accessControlList().grants().get(0).grantee().type(), + policy.accessControlList().grants().get(0).grantee().type(), + GranteeType.CANONICAL_USER); + Assert.assertEquals( + "permission: expected: " + + Permission.FULL_CONTROL + + ", got: " + + policy.accessControlList().grants().get(0).permission(), + policy.accessControlList().grants().get(0).permission(), + Permission.FULL_CONTROL); + mintSuccessLog(methodName, null, startTime); + } finally { + client.removeObject( + RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build()); + } + } catch (Exception e) { + handleException(methodName, null, startTime, e); + } + } + + public static void getObjectAttributes() throws Exception { + String methodName = "getObjectAttributes()"; + if (!mintEnv) { + System.out.println(methodName); + } + + long startTime = System.currentTimeMillis(); + String objectName = getRandomName(); + try { + try { + client.putObject( + PutObjectArgs.builder().bucket(bucketName).object(objectName).stream( + new ContentInputStream(1 * KB), 1 * KB, -1) + .build()); + GetObjectAttributesResponse response = + client.getObjectAttributes( + GetObjectAttributesArgs.builder() + .bucket(bucketName) + .object(objectName) + .objectAttributes( + new String[] { + "ETag", "Checksum", "ObjectParts", "StorageClass", "ObjectSize" + }) + .build()); + Assert.assertTrue( + "objectSize: expected: " + (1 * KB) + ", got: " + response.result().objectSize(), + response.result().objectSize() == (1 * KB)); + Assert.assertTrue( + "partNumber: expected: 1, got: " + + response.result().objectParts().parts().get(0).partNumber(), + response.result().objectParts().parts().get(0).partNumber() == 1); + Assert.assertTrue( + "partSize: expected: " + + (1 * KB) + + ", got: " + + response.result().objectParts().parts().get(0).partSize(), + response.result().objectParts().parts().get(0).partSize() == (1 * KB)); + mintSuccessLog(methodName, null, startTime); + } finally { + client.removeObject( + RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build()); + } + } catch (Exception e) { + handleException(methodName, null, startTime, e); + } + } + public static void setBucketReplication() throws Exception { String methodName = "setBucketReplication()"; if (!mintEnv) { @@ -3704,6 +3870,60 @@ public static void uploadSnowballObjects() throws Exception { testUploadSnowballObjects("[compression]", true); } + public static void putObjectFanOut() throws Exception { + String methodName = "putObjectFanOut()"; + if (!mintEnv) { + System.out.println(methodName); + } + + long startTime = System.currentTimeMillis(); + String objectName1 = getRandomName(); + String objectName2 = getRandomName(); + try { + try { + Map map = new HashMap<>(); + map.put("Project", "Project One"); + map.put("User", "jsmith"); + client.putObjectFanOut( + PutObjectFanOutArgs.builder().bucket(bucketName).stream( + new ByteArrayInputStream("hello".getBytes(StandardCharsets.UTF_8)), 5) + .entries( + Arrays.asList( + new PutObjectFanOutEntry[] { + PutObjectFanOutEntry.builder().key(objectName1).userMetadata(map).build(), + PutObjectFanOutEntry.builder().key(objectName2).tags(map).build() + })) + .build()); + + StatObjectResponse stat = + client.statObject( + StatObjectArgs.builder().bucket(bucketName).object(objectName1).build()); + Assert.assertTrue( + "userMetadata: expected = " + map + ", got = " + stat.userMetadata(), + map.size() == stat.userMetadata().size() + && map.entrySet().stream() + .allMatch( + e -> + e.getValue() + .equals( + stat.userMetadata().get(e.getKey().toLowerCase(Locale.US))))); + + Tags tags = + client.getObjectTags( + GetObjectTagsArgs.builder().bucket(bucketName).object(objectName2).build()); + Assert.assertTrue( + "tags: expected = " + map + ", got = " + tags.get(), map.equals(tags.get())); + } finally { + client.removeObject( + RemoveObjectArgs.builder().bucket(bucketName).object(objectName1).build()); + client.removeObject( + RemoveObjectArgs.builder().bucket(bucketName).object(objectName2).build()); + } + } catch (Exception e) { + handleException(methodName, null, startTime, e); + } + } + public static void runBucketTests() throws Exception { makeBucket(); bucketExists(); @@ -3720,6 +3940,10 @@ public static void runBucketTests() throws Exception { getBucketEncryption(); deleteBucketEncryption(); + setBucketCors(); + getBucketCors(); + deleteBucketCors(); + setBucketTags(); getBucketTags(); deleteBucketTags(); @@ -3775,7 +3999,11 @@ public static void runObjectTests() throws Exception { getObjectTags(); deleteObjectTags(); + getObjectAcl(); + getObjectAttributes(); + uploadSnowballObjects(); + putObjectFanOut(); teardown(); }