diff --git a/adminapi/src/main/java/io/minio/admin/MinioAdminClient.java b/adminapi/src/main/java/io/minio/admin/MinioAdminClient.java index de11bd275..85193165b 100644 --- a/adminapi/src/main/java/io/minio/admin/MinioAdminClient.java +++ b/adminapi/src/main/java/io/minio/admin/MinioAdminClient.java @@ -27,18 +27,16 @@ import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Multimap; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import io.minio.Digest; -import io.minio.MinioProperties; -import io.minio.S3Escaper; +import io.minio.Checksum; +import io.minio.Http; import io.minio.Signer; import io.minio.Time; +import io.minio.Utils; import io.minio.admin.messages.DataUsageInfo; import io.minio.admin.messages.info.Message; import io.minio.credentials.Credentials; import io.minio.credentials.Provider; import io.minio.credentials.StaticProvider; -import io.minio.http.HttpUtils; -import io.minio.http.Method; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; @@ -115,7 +113,7 @@ public String toString() { OBJECT_MAPPER.registerModule(new JavaTimeModule()); } - private String userAgent = MinioProperties.INSTANCE.getDefaultUserAgent(); + private String userAgent = Utils.getDefaultUserAgent(); private PrintWriter traceStream; private HttpUrl baseUrl; @@ -138,7 +136,7 @@ private Credentials getCredentials() { } private Response execute( - Method method, Command command, Multimap queryParamMap, byte[] body) + Http.Method method, Command command, Multimap queryParamMap, byte[] body) throws InvalidKeyException, IOException, NoSuchAlgorithmException { Credentials creds = getCredentials(); @@ -146,32 +144,33 @@ private Response execute( this.baseUrl .newBuilder() .host(this.baseUrl.host()) - .addEncodedPathSegments(S3Escaper.encodePath("minio/admin/v3/" + command.toString())); + .addEncodedPathSegments(Utils.encodePath("minio/admin/v3/" + command.toString())); if (queryParamMap != null) { for (Map.Entry entry : queryParamMap.entries()) { urlBuilder.addEncodedQueryParameter( - S3Escaper.encode(entry.getKey()), S3Escaper.encode(entry.getValue())); + Utils.encode(entry.getKey()), Utils.encode(entry.getValue())); } } HttpUrl url = urlBuilder.build(); Request.Builder requestBuilder = new Request.Builder(); requestBuilder.url(url); - requestBuilder.header("Host", HttpUtils.getHostHeader(url)); + requestBuilder.header("Host", Utils.getHostHeader(url)); requestBuilder.header("Accept-Encoding", "identity"); // Disable default gzip compression. requestBuilder.header("User-Agent", this.userAgent); requestBuilder.header("x-amz-date", ZonedDateTime.now().format(Time.AMZ_DATE_FORMAT)); if (creds.sessionToken() != null) { requestBuilder.header("X-Amz-Security-Token", creds.sessionToken()); } - if (body == null && (method != Method.GET && method != Method.HEAD)) { - body = HttpUtils.EMPTY_BODY; + if (body == null && (method != Http.Method.GET && method != Http.Method.HEAD)) { + body = Utils.EMPTY_BODY; } if (body != null) { - requestBuilder.header("x-amz-content-sha256", Digest.sha256Hash(body, body.length)); + requestBuilder.header( + "x-amz-content-sha256", Checksum.hexString(Checksum.SHA256.sum(body, 0, body.length))); requestBuilder.method(method.toString(), RequestBody.create(body, DEFAULT_MEDIA_TYPE)); } else { - requestBuilder.header("x-amz-content-sha256", Digest.ZERO_SHA256_HASH); + requestBuilder.header("x-amz-content-sha256", Checksum.ZERO_SHA256_HASH); } Request request = requestBuilder.build(); @@ -251,7 +250,7 @@ public void addUser( Credentials creds = getCredentials(); try (Response response = execute( - Method.PUT, + Http.Method.PUT, Command.ADD_USER, ImmutableMultimap.of("accessKey", accessKey), Crypto.encrypt(OBJECT_MAPPER.writeValueAsBytes(userInfo), creds.secretKey()))) {} @@ -270,7 +269,10 @@ public UserInfo getUserInfo(String accessKey) throws NoSuchAlgorithmException, InvalidKeyException, IOException { try (Response response = execute( - Method.GET, Command.USER_INFO, ImmutableMultimap.of("accessKey", accessKey), null)) { + Http.Method.GET, + Command.USER_INFO, + ImmutableMultimap.of("accessKey", accessKey), + null)) { byte[] jsonData = response.body().bytes(); return OBJECT_MAPPER.readValue(jsonData, UserInfo.class); } @@ -288,7 +290,7 @@ public UserInfo getUserInfo(String accessKey) public Map listUsers() throws NoSuchAlgorithmException, InvalidKeyException, IOException, InvalidCipherTextException { - try (Response response = execute(Method.GET, Command.LIST_USERS, null, null)) { + try (Response response = execute(Http.Method.GET, Command.LIST_USERS, null, null)) { Credentials creds = getCredentials(); byte[] jsonData = Crypto.decrypt(response.body().byteStream(), creds.secretKey()); MapType mapType = @@ -315,7 +317,7 @@ public void deleteUser(@Nonnull String accessKey) try (Response response = execute( - Method.DELETE, + Http.Method.DELETE, Command.REMOVE_USER, ImmutableMultimap.of("accessKey", accessKey), null)) {} @@ -342,7 +344,7 @@ public void addUpdateGroup( try (Response response = execute( - Method.PUT, + Http.Method.PUT, Command.ADD_UPDATE_REMOVE_GROUP, null, OBJECT_MAPPER.writeValueAsBytes(groupAddUpdateRemoveInfo))) {} @@ -360,7 +362,7 @@ public void addUpdateGroup( public GroupInfo getGroupInfo(String group) throws NoSuchAlgorithmException, InvalidKeyException, IOException { try (Response response = - execute(Method.GET, Command.GROUP_INFO, ImmutableMultimap.of("group", group), null)) { + execute(Http.Method.GET, Command.GROUP_INFO, ImmutableMultimap.of("group", group), null)) { byte[] jsonData = response.body().bytes(); return OBJECT_MAPPER.readValue(jsonData, GroupInfo.class); } @@ -376,7 +378,7 @@ public GroupInfo getGroupInfo(String group) */ public List listGroups() throws NoSuchAlgorithmException, InvalidKeyException, IOException { - try (Response response = execute(Method.GET, Command.LIST_GROUPS, null, null)) { + try (Response response = execute(Http.Method.GET, Command.LIST_GROUPS, null, null)) { byte[] jsonData = response.body().bytes(); CollectionType mapType = OBJECT_MAPPER.getTypeFactory().constructCollectionType(ArrayList.class, String.class); @@ -402,7 +404,7 @@ public void removeGroup(@Nonnull String group) try (Response response = execute( - Method.PUT, + Http.Method.PUT, Command.ADD_UPDATE_REMOVE_GROUP, null, OBJECT_MAPPER.writeValueAsBytes(groupAddUpdateRemoveInfo))) {} @@ -427,7 +429,7 @@ public void setBucketQuota(@Nonnull String bucketName, long size, @Nonnull Quota quotaEntity.put("quota", unit.toBytes(size)); try (Response response = execute( - Method.PUT, + Http.Method.PUT, Command.SET_BUCKET_QUOTA, ImmutableMultimap.of("bucket", bucketName), OBJECT_MAPPER.writeValueAsBytes(quotaEntity))) {} @@ -446,7 +448,7 @@ public long getBucketQuota(String bucketName) throws IOException, NoSuchAlgorithmException, InvalidKeyException { try (Response response = execute( - Method.GET, + Http.Method.GET, Command.GET_BUCKET_QUOTA, ImmutableMultimap.of("bucket", bucketName), null)) { @@ -513,7 +515,7 @@ public void addCannedPolicy(@Nonnull String name, @Nonnull String policy) try (Response response = execute( - Method.PUT, + Http.Method.PUT, Command.ADD_CANNED_POLICY, ImmutableMultimap.of("name", name), policy.getBytes(StandardCharsets.UTF_8))) {} @@ -541,7 +543,7 @@ public void setPolicy( try (Response response = execute( - Method.PUT, + Http.Method.PUT, Command.SET_USER_OR_GROUP_POLICY, ImmutableMultimap.of( "userOrGroup", @@ -564,7 +566,7 @@ public void setPolicy( */ public Map listCannedPolicies() throws NoSuchAlgorithmException, InvalidKeyException, IOException { - try (Response response = execute(Method.GET, Command.LIST_CANNED_POLICIES, null, null)) { + try (Response response = execute(Http.Method.GET, Command.LIST_CANNED_POLICIES, null, null)) { MapType mapType = OBJECT_MAPPER .getTypeFactory() @@ -593,7 +595,7 @@ public void removeCannedPolicy(@Nonnull String name) try (Response response = execute( - Method.DELETE, + Http.Method.DELETE, Command.REMOVE_CANNED_POLICY, ImmutableMultimap.of("name", name), null)) {} @@ -609,7 +611,7 @@ public void removeCannedPolicy(@Nonnull String name) */ public DataUsageInfo getDataUsageInfo() throws IOException, NoSuchAlgorithmException, InvalidKeyException { - try (Response response = execute(Method.GET, Command.DATA_USAGE_INFO, null, null)) { + try (Response response = execute(Http.Method.GET, Command.DATA_USAGE_INFO, null, null)) { return OBJECT_MAPPER.readValue(response.body().bytes(), DataUsageInfo.class); } } @@ -623,7 +625,7 @@ public DataUsageInfo getDataUsageInfo() * @throws IOException thrown to indicate I/O error on MinIO REST operation. */ public Message getServerInfo() throws IOException, NoSuchAlgorithmException, InvalidKeyException { - try (Response response = execute(Method.GET, Command.INFO, null, null)) { + try (Response response = execute(Http.Method.GET, Command.INFO, null, null)) { return OBJECT_MAPPER.readValue(response.body().charStream(), Message.class); } } @@ -685,13 +687,13 @@ public Credentials addServiceAccount( serviceAccount.put("description", description); } if (expiration != null) { - serviceAccount.put("expiration", expiration.format(Time.EXPIRATION_DATE_FORMAT)); + serviceAccount.put("expiration", expiration.format(Time.ISO8601UTC_FORMAT)); } Credentials creds = getCredentials(); try (Response response = execute( - Method.PUT, + Http.Method.PUT, Command.ADD_SERVICE_ACCOUNT, null, Crypto.encrypt(OBJECT_MAPPER.writeValueAsBytes(serviceAccount), creds.secretKey()))) { @@ -752,13 +754,13 @@ public void updateServiceAccount( serviceAccount.put("newDescription", newDescription); } if (newExpiration != null) { - serviceAccount.put("newExpiration", newExpiration.format(Time.EXPIRATION_DATE_FORMAT)); + serviceAccount.put("newExpiration", newExpiration.format(Time.ISO8601UTC_FORMAT)); } Credentials creds = getCredentials(); try (Response response = execute( - Method.POST, + Http.Method.POST, Command.UPDATE_SERVICE_ACCOUNT, ImmutableMultimap.of("accessKey", accessKey), Crypto.encrypt(OBJECT_MAPPER.writeValueAsBytes(serviceAccount), creds.secretKey()))) {} @@ -780,7 +782,7 @@ public void deleteServiceAccount(@Nonnull String accessKey) try (Response response = execute( - Method.DELETE, + Http.Method.DELETE, Command.DELETE_SERVICE_ACCOUNT, ImmutableMultimap.of("accessKey", accessKey), null)) {} @@ -805,7 +807,7 @@ public ListServiceAccountResp listServiceAccount(@Nonnull String username) try (Response response = execute( - Method.GET, + Http.Method.GET, Command.LIST_SERVICE_ACCOUNTS, ImmutableMultimap.of("user", username), null)) { @@ -833,7 +835,7 @@ public GetServiceAccountInfoResp getServiceAccountInfo(@Nonnull String accessKey } try (Response response = execute( - Method.GET, + Http.Method.GET, Command.INFO_SERVICE_ACCOUNT, ImmutableMultimap.of("accessKey", accessKey), null)) { @@ -857,8 +859,7 @@ public GetServiceAccountInfoResp getServiceAccountInfo(@Nonnull String accessKey * @param readTimeout HTTP read timeout in milliseconds. */ public void setTimeout(long connectTimeout, long writeTimeout, long readTimeout) { - this.httpClient = - HttpUtils.setTimeout(this.httpClient, connectTimeout, writeTimeout, readTimeout); + this.httpClient = Http.setTimeout(this.httpClient, connectTimeout, writeTimeout, readTimeout); } /** @@ -873,7 +874,7 @@ public void setTimeout(long connectTimeout, long writeTimeout, long readTimeout) */ @SuppressFBWarnings(value = "SIC", justification = "Should not be used in production anyways.") public void ignoreCertCheck() throws KeyManagementException, NoSuchAlgorithmException { - this.httpClient = HttpUtils.disableCertCheck(this.httpClient); + this.httpClient = Http.disableCertCheck(this.httpClient); } /** @@ -885,8 +886,7 @@ public void ignoreCertCheck() throws KeyManagementException, NoSuchAlgorithmExce */ public void setAppInfo(String name, String version) { if (name == null || version == null) return; - this.userAgent = - MinioProperties.INSTANCE.getDefaultUserAgent() + " " + name.trim() + "/" + version.trim(); + this.userAgent = Utils.getDefaultUserAgent() + " " + name.trim() + "/" + version.trim(); } /** @@ -923,12 +923,12 @@ public static final class Builder { private OkHttpClient httpClient; public Builder endpoint(String endpoint) { - this.baseUrl = HttpUtils.getBaseUrl(endpoint); + this.baseUrl = Utils.getBaseUrl(endpoint); return this; } public Builder endpoint(String endpoint, int port, boolean secure) { - HttpUrl url = HttpUtils.getBaseUrl(endpoint); + HttpUrl url = Utils.getBaseUrl(endpoint); if (port < 1 || port > 65535) { throw new IllegalArgumentException("port must be in range of 1 to 65535"); } @@ -938,20 +938,20 @@ public Builder endpoint(String endpoint, int port, boolean secure) { } public Builder endpoint(HttpUrl url) { - HttpUtils.validateNotNull(url, "url"); - HttpUtils.validateUrl(url); + Utils.validateNotNull(url, "url"); + Utils.validateUrl(url); this.baseUrl = url; return this; } public Builder endpoint(URL url) { - HttpUtils.validateNotNull(url, "url"); + Utils.validateNotNull(url, "url"); return endpoint(HttpUrl.get(url)); } public Builder region(String region) { - HttpUtils.validateNotNull(region, "region"); + Utils.validateNotNull(region, "region"); this.region = region; return this; } @@ -962,24 +962,22 @@ public Builder credentials(String accessKey, String secretKey) { } public Builder credentialsProvider(Provider provider) { - HttpUtils.validateNotNull(provider, "credential provider"); + Utils.validateNotNull(provider, "credential provider"); this.provider = provider; return this; } public Builder httpClient(OkHttpClient httpClient) { - HttpUtils.validateNotNull(httpClient, "http client"); + Utils.validateNotNull(httpClient, "http client"); this.httpClient = httpClient; return this; } public MinioAdminClient build() { - HttpUtils.validateNotNull(baseUrl, "base url"); - HttpUtils.validateNotNull(provider, "credential provider"); + Utils.validateNotNull(baseUrl, "base url"); + Utils.validateNotNull(provider, "credential provider"); if (httpClient == null) { - httpClient = - HttpUtils.newDefaultHttpClient( - DEFAULT_CONNECTION_TIMEOUT, DEFAULT_CONNECTION_TIMEOUT, DEFAULT_CONNECTION_TIMEOUT); + httpClient = Http.newDefaultClient(); } return new MinioAdminClient(baseUrl, region, provider, httpClient); } diff --git a/api/src/main/java/io/minio/AbortMultipartUploadArgs.java b/api/src/main/java/io/minio/AbortMultipartUploadArgs.java new file mode 100644 index 000000000..6405f1657 --- /dev/null +++ b/api/src/main/java/io/minio/AbortMultipartUploadArgs.java @@ -0,0 +1,64 @@ +/* + * 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.Objects; + +/** + * Argument class of {@link MinioAsyncClient#abortMultipartUpload} and {@link + * MinioClient#abortMultipartUpload}. + */ +public class AbortMultipartUploadArgs extends ObjectArgs { + private String uploadId; + + public String uploadId() { + return uploadId; + } + + public static Builder builder() { + return new Builder(); + } + + /** Argument builder of {@link AbortMultipartUploadArgs}. */ + public static final class Builder extends ObjectArgs.Builder { + @Override + protected void validate(AbortMultipartUploadArgs args) { + super.validate(args); + Utils.validateNotEmptyString(args.uploadId, "upload ID"); + } + + public Builder uploadId(String uploadId) { + Utils.validateNotEmptyString(uploadId, "upload ID"); + operations.add(args -> args.uploadId = uploadId); + return this; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof AbortMultipartUploadArgs)) return false; + if (!super.equals(o)) return false; + AbortMultipartUploadArgs that = (AbortMultipartUploadArgs) o; + return Objects.equals(uploadId, that.uploadId); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), uploadId); + } +} diff --git a/api/src/main/java/io/minio/BaseArgs.java b/api/src/main/java/io/minio/BaseArgs.java index c8857ec55..ba9b8875f 100644 --- a/api/src/main/java/io/minio/BaseArgs.java +++ b/api/src/main/java/io/minio/BaseArgs.java @@ -60,75 +60,34 @@ public abstract static class Builder, A extends BaseArgs protected abstract void validate(A args); - protected void validateNotNull(Object arg, String argName) { - if (arg == null) { - throw new IllegalArgumentException(argName + " must not be null."); - } - } - - protected void validateNotEmptyString(String arg, String argName) { - validateNotNull(arg, argName); - if (arg.isEmpty()) { - throw new IllegalArgumentException(argName + " must be a non-empty string."); - } - } - - protected void validateNullOrNotEmptyString(String arg, String argName) { - if (arg != null && arg.isEmpty()) { - throw new IllegalArgumentException(argName + " must be a non-empty string."); - } - } - - protected void validateNullOrPositive(Number arg, String argName) { - if (arg != null && arg.longValue() < 0) { - throw new IllegalArgumentException(argName + " cannot be non-negative."); - } - } - public Builder() { this.operations = new ArrayList<>(); } - protected Multimap copyMultimap(Multimap multimap) { - Multimap multimapCopy = HashMultimap.create(); - if (multimap != null) { - multimapCopy.putAll(multimap); - } - return Multimaps.unmodifiableMultimap(multimapCopy); - } - - protected Multimap toMultimap(Map map) { - Multimap multimap = HashMultimap.create(); - if (map != null) { - multimap.putAll(Multimaps.forMap(map)); - } - return Multimaps.unmodifiableMultimap(multimap); - } - @SuppressWarnings("unchecked") // Its safe to type cast to B as B extends this class. public B extraHeaders(Multimap headers) { - final Multimap extraHeaders = copyMultimap(headers); + final Multimap extraHeaders = Utils.newMultimap(headers); operations.add(args -> args.extraHeaders = extraHeaders); return (B) this; } @SuppressWarnings("unchecked") // Its safe to type cast to B as B extends this class. public B extraQueryParams(Multimap queryParams) { - final Multimap extraQueryParams = copyMultimap(queryParams); + final Multimap extraQueryParams = Utils.newMultimap(queryParams); operations.add(args -> args.extraQueryParams = extraQueryParams); return (B) this; } @SuppressWarnings("unchecked") // Its safe to type cast to B as B extends this class. public B extraHeaders(Map headers) { - final Multimap extraHeaders = toMultimap(headers); + final Multimap extraHeaders = Utils.newMultimap(headers); operations.add(args -> args.extraHeaders = extraHeaders); return (B) this; } @SuppressWarnings("unchecked") // Its safe to type cast to B as B extends this class. public B extraQueryParams(Map queryParams) { - final Multimap extraQueryParams = toMultimap(queryParams); + final Multimap extraQueryParams = Utils.newMultimap(queryParams); operations.add(args -> args.extraQueryParams = extraQueryParams); return (B) this; } diff --git a/api/src/main/java/io/minio/BaseClient.java b/api/src/main/java/io/minio/BaseClient.java new file mode 100644 index 000000000..820ba843b --- /dev/null +++ b/api/src/main/java/io/minio/BaseClient.java @@ -0,0 +1,706 @@ +/* + * 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.JsonParseException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; +import io.minio.credentials.Credentials; +import io.minio.credentials.Provider; +import io.minio.errors.ErrorResponseException; +import io.minio.errors.InsufficientDataException; +import io.minio.errors.InternalException; +import io.minio.errors.InvalidResponseException; +import io.minio.errors.ServerException; +import io.minio.errors.XmlParserException; +import io.minio.messages.CompleteMultipartUpload; +import io.minio.messages.CompleteMultipartUploadResult; +import io.minio.messages.CopyPartResult; +import io.minio.messages.DeleteRequest; +import io.minio.messages.DeleteResult; +import io.minio.messages.ErrorResponse; +import io.minio.messages.InitiateMultipartUploadResult; +import io.minio.messages.Item; +import io.minio.messages.ListAllMyBucketsResult; +import io.minio.messages.ListBucketResultV1; +import io.minio.messages.ListBucketResultV2; +import io.minio.messages.ListMultipartUploadsResult; +import io.minio.messages.ListObjectsResult; +import io.minio.messages.ListPartsResult; +import io.minio.messages.ListVersionsResult; +import io.minio.messages.LocationConstraint; +import io.minio.messages.NotificationRecords; +import io.minio.messages.Part; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.RandomAccessFile; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +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; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.logging.Logger; +import javax.annotation.Nonnull; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.ResponseBody; + +/** Core S3 API client. */ +public abstract class BaseClient extends BaseS3Client { + ///////////////////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////// Higher level ListObjects implementation /////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////////////////// + + /** Throws encapsulated exception wrapped by {@link ExecutionException}. */ + public void throwEncapsulatedException(ExecutionException e) + throws ErrorResponseException, InsufficientDataException, InternalException, + InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException, + ServerException, XmlParserException { + if (e == null) return; + + Throwable ex = e.getCause(); + + if (ex instanceof CompletionException) { + ex = ((CompletionException) ex).getCause(); + } + + if (ex instanceof ExecutionException) { + ex = ((ExecutionException) ex).getCause(); + } + + try { + throw ex; + } catch (IllegalArgumentException + | ErrorResponseException + | InsufficientDataException + | InternalException + | InvalidKeyException + | InvalidResponseException + | IOException + | NoSuchAlgorithmException + | ServerException + | XmlParserException exc) { + throw exc; + } catch (Throwable exc) { + throw new RuntimeException(exc.getCause() == null ? exc : exc.getCause()); + } + } + + private abstract class ObjectIterator implements Iterator> { + protected Result error; + protected Iterator itemIterator; + protected Iterator deleteMarkerIterator; + protected Iterator prefixIterator; + protected boolean completed = false; + protected ListObjectsResult listObjectsResult; + protected String lastObjectName; + + protected abstract void populateResult() + throws ErrorResponseException, InsufficientDataException, InternalException, + InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException, + ServerException, XmlParserException; + + protected synchronized void populate() { + try { + populateResult(); + } catch (ErrorResponseException + | InsufficientDataException + | InternalException + | InvalidKeyException + | InvalidResponseException + | IOException + | NoSuchAlgorithmException + | ServerException + | XmlParserException e) { + this.error = new Result<>(e); + } + + if (this.listObjectsResult != null) { + this.itemIterator = this.listObjectsResult.contents().iterator(); + this.deleteMarkerIterator = this.listObjectsResult.deleteMarkers().iterator(); + this.prefixIterator = this.listObjectsResult.commonPrefixes().iterator(); + } else { + this.itemIterator = new LinkedList().iterator(); + this.deleteMarkerIterator = new LinkedList().iterator(); + this.prefixIterator = new LinkedList().iterator(); + } + } + + @Override + public boolean hasNext() { + if (this.completed) return false; + + if (this.error == null + && this.itemIterator == null + && this.deleteMarkerIterator == null + && this.prefixIterator == null) { + populate(); + } + + if (this.error == null + && !this.itemIterator.hasNext() + && !this.deleteMarkerIterator.hasNext() + && !this.prefixIterator.hasNext() + && this.listObjectsResult.isTruncated()) { + populate(); + } + + if (this.error != null) return true; + if (this.itemIterator.hasNext()) return true; + if (this.deleteMarkerIterator.hasNext()) return true; + if (this.prefixIterator.hasNext()) return true; + + this.completed = true; + return false; + } + + @Override + public Result next() { + if (this.completed) throw new NoSuchElementException(); + if (this.error == null + && this.itemIterator == null + && this.deleteMarkerIterator == null + && this.prefixIterator == null) { + populate(); + } + + if (this.error == null + && !this.itemIterator.hasNext() + && !this.deleteMarkerIterator.hasNext() + && !this.prefixIterator.hasNext() + && this.listObjectsResult.isTruncated()) { + populate(); + } + + if (this.error != null) { + this.completed = true; + return this.error; + } + + Item item = null; + if (this.itemIterator.hasNext()) { + item = this.itemIterator.next(); + item.setEncodingType(this.listObjectsResult.encodingType()); + this.lastObjectName = item.objectName(); + } else if (this.deleteMarkerIterator.hasNext()) { + item = this.deleteMarkerIterator.next(); + } else if (this.prefixIterator.hasNext()) { + item = this.prefixIterator.next().toItem(); + } + + if (item != null) { + item.setEncodingType(this.listObjectsResult.encodingType()); + return new Result<>(item); + } + + this.completed = true; + throw new NoSuchElementException(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + + /** Execute list objects v1. */ + protected Iterable> listObjectsV1(ListObjectsV1Args args) { + return new Iterable>() { + @Override + public Iterator> iterator() { + return new ObjectIterator() { + private ListBucketResultV1 result = null; + + @Override + protected void populateResult() + throws ErrorResponseException, InsufficientDataException, InternalException, + InvalidKeyException, InvalidResponseException, IOException, + NoSuchAlgorithmException, ServerException, XmlParserException { + this.listObjectsResult = null; + this.itemIterator = null; + this.prefixIterator = null; + + String nextMarker = (result == null) ? args.marker() : result.nextMarker(); + if (nextMarker == null) nextMarker = this.lastObjectName; + + try { + ListObjectsV1Response response = + listObjectsV1( + ListObjectsV1Args.builder() + .extraHeaders(args.extraHeaders()) + .extraQueryParams(args.extraQueryParams()) + .bucket(args.bucket()) + .region(args.region()) + .delimiter(args.delimiter()) + .encodingType(args.encodingType()) + .maxKeys(args.maxKeys()) + .prefix(args.prefix()) + .marker(nextMarker) + .build()) + .get(); + result = response.result(); + this.listObjectsResult = response.result(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + throwEncapsulatedException(e); + } + } + }; + } + }; + } + + /** Execute list objects v2. */ + protected Iterable> listObjectsV2(ListObjectsV2Args args) { + return new Iterable>() { + @Override + public Iterator> iterator() { + return new ObjectIterator() { + private ListBucketResultV2 result = null; + + @Override + protected void populateResult() + throws ErrorResponseException, InsufficientDataException, InternalException, + InvalidKeyException, InvalidResponseException, IOException, + NoSuchAlgorithmException, ServerException, XmlParserException { + this.listObjectsResult = null; + this.itemIterator = null; + this.prefixIterator = null; + + try { + ListObjectsV2Response response = + listObjectsV2( + ListObjectsV2Args.builder() + .extraHeaders(args.extraHeaders()) + .extraQueryParams(args.extraQueryParams()) + .bucket(args.bucket()) + .region(args.region()) + .delimiter(args.delimiter()) + .encodingType(args.encodingType()) + .maxKeys(args.maxKeys()) + .prefix(args.prefix()) + .startAfter(args.startAfter()) + .continuationToken( + result == null + ? args.continuationToken() + : result.nextContinuationToken()) + .fetchOwner(args.fetchOwner()) + .includeUserMetadata(args.includeUserMetadata()) + .build()) + .get(); + result = response.result(); + this.listObjectsResult = response.result(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + throwEncapsulatedException(e); + } + } + }; + } + }; + } + + /** Execute list object versions. */ + protected Iterable> listObjectVersions(ListObjectVersionsArgs args) { + return new Iterable>() { + @Override + public Iterator> iterator() { + return new ObjectIterator() { + private ListVersionsResult result = null; + + @Override + protected void populateResult() + throws ErrorResponseException, InsufficientDataException, InternalException, + InvalidKeyException, InvalidResponseException, IOException, + NoSuchAlgorithmException, ServerException, XmlParserException { + this.listObjectsResult = null; + this.itemIterator = null; + this.prefixIterator = null; + + try { + ListObjectVersionsResponse response = + listObjectVersions( + ListObjectVersionsArgs.builder() + .extraHeaders(args.extraHeaders()) + .extraQueryParams(args.extraQueryParams()) + .bucket(args.bucket()) + .region(args.region()) + .delimiter(args.delimiter()) + .encodingType(args.encodingType()) + .maxKeys(args.maxKeys()) + .prefix(args.prefix()) + .keyMarker(result == null ? args.keyMarker() : result.nextKeyMarker()) + .versionIdMarker( + result == null + ? args.versionIdMarker() + : result.nextVersionIdMarker()) + .build()) + .get(); + result = response.result(); + this.listObjectsResult = response.result(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + throwEncapsulatedException(e); + } + } + }; + } + }; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////// ListenBucketNotification API implementation ///////////////////////// + ///////////////////////////////////////////////////////////////////////////////////////////////// + + /** Notification result records representation. */ + protected static class NotificationResultRecords { + Response response = null; + Scanner scanner = null; + ObjectMapper mapper = null; + + public NotificationResultRecords(Response response) { + this.response = response; + this.scanner = new Scanner(response.body().charStream()).useDelimiter("\n"); + this.mapper = + JsonMapper.builder() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true) + .build(); + } + + /** returns closeable iterator of result of notification records. */ + public CloseableIterator> closeableIterator() { + return new CloseableIterator>() { + String recordsString = null; + NotificationRecords records = null; + boolean isClosed = false; + + @Override + public void close() throws IOException { + if (!isClosed) { + try { + response.body().close(); + scanner.close(); + } finally { + isClosed = true; + } + } + } + + public boolean populate() { + if (isClosed) return false; + if (recordsString != null) return true; + + while (scanner.hasNext()) { + recordsString = scanner.next().trim(); + if (!recordsString.equals("")) break; + } + + if (recordsString == null || recordsString.equals("")) { + try { + close(); + } catch (IOException e) { + isClosed = true; + } + return false; + } + return true; + } + + @Override + public boolean hasNext() { + return populate(); + } + + @Override + public Result next() { + if (isClosed) throw new NoSuchElementException(); + if ((recordsString == null || recordsString.equals("")) && !populate()) { + throw new NoSuchElementException(); + } + + try { + records = mapper.readValue(recordsString, NotificationRecords.class); + return new Result<>(records); + } catch (JsonMappingException e) { + return new Result<>(e); + } catch (JsonParseException e) { + return new Result<>(e); + } catch (IOException e) { + return new Result<>(e); + } finally { + recordsString = null; + records = null; + } + } + }; + } + } + + ///////////////////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////////////////// + + private Part[] uploadParts( + PutObjectBaseArgs args, String uploadId, PartReader partReader, PartSource firstPartSource) + throws InterruptedException, ExecutionException, InsufficientDataException, InternalException, + InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { + Part[] parts = new Part[ObjectWriteArgs.MAX_MULTIPART_COUNT]; + int partNumber = 0; + PartSource partSource = firstPartSource; + while (true) { + partNumber++; + + Multimap ssecHeaders = null; + // set encryption headers in the case of SSE-C. + if (args.sse() != null && args.sse() instanceof ServerSideEncryption.CustomerKey) { + ssecHeaders = Multimaps.forMap(args.sse().headers()); + } + + UploadPartResponse response = + uploadPart( + UploadPartArgs.builder() + .bucket(args.bucket()) + .region(args.region()) + .object(args.object()) + .buffer(partSource, partSource.size()) + .partNumber(partNumber) + .uploadId(uploadId) + .headers(ssecHeaders) + .build()) + .get(); + parts[partNumber - 1] = new Part(partNumber, response.etag()); + + partSource = partReader.getPart(); + if (partSource == null) break; + } + + return parts; + } + + private CompletableFuture putMultipartObjectAsync( + PutObjectBaseArgs args, + Multimap headers, + PartReader partReader, + PartSource firstPartSource) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + return CompletableFuture.supplyAsync( + () -> { + String uploadId = null; + ObjectWriteResponse response = null; + try { + CreateMultipartUploadResponse createMultipartUploadResponse = + createMultipartUpload( + CreateMultipartUploadArgs.builder() + .extraQueryParams(args.extraQueryParams()) + .bucket(args.bucket()) + .region(args.region()) + .object(args.object()) + .headers(headers) + .build()) + .get(); + uploadId = createMultipartUploadResponse.result().uploadId(); + Part[] parts = uploadParts(args, uploadId, partReader, firstPartSource); + response = + completeMultipartUpload( + CompleteMultipartUploadArgs.builder() + .bucket(args.bucket()) + .region(args.region()) + .object(args.object()) + .uploadId(uploadId) + .parts(parts) + .build()) + .get(); + } catch (InsufficientDataException + | InternalException + | InvalidKeyException + | IOException + | NoSuchAlgorithmException + | XmlParserException + | InterruptedException + | ExecutionException e) { + Throwable throwable = e; + if (throwable instanceof ExecutionException) { + throwable = ((ExecutionException) throwable).getCause(); + } + if (throwable instanceof CompletionException) { + throwable = ((CompletionException) throwable).getCause(); + } + if (uploadId == null) { + throw new CompletionException(throwable); + } + try { + abortMultipartUpload( + AbortMultipartUploadArgs.builder() + .bucket(args.bucket()) + .region(args.region()) + .object(args.object()) + .uploadId(uploadId) + .build()) + .get(); + } catch (InsufficientDataException + | InternalException + | InvalidKeyException + | IOException + | NoSuchAlgorithmException + | XmlParserException + | InterruptedException + | ExecutionException ex) { + throwable = ex; + if (throwable instanceof ExecutionException) { + throwable = ((ExecutionException) throwable).getCause(); + } + if (throwable instanceof CompletionException) { + throwable = ((CompletionException) throwable).getCause(); + } + } + throw new CompletionException(throwable); + } + return response; + }); + } + + protected PartReader newPartReader( + Object data, long objectSize, long partSize, int partCount, Checksum.Algorithm... algorithms) + throws NoSuchAlgorithmException { + if (data instanceof RandomAccessFile) { + return new PartReader((RandomAccessFile) data, objectSize, partSize, partCount, algorithms); + } + + if (data instanceof InputStream) { + return new PartReader((InputStream) data, objectSize, partSize, partCount, algorithms); + } + + return null; + } + + protected CompletableFuture calculatePartCountAsync(List sources) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + long[] objectSize = {0}; + int index = 0; + + CompletableFuture completableFuture = CompletableFuture.supplyAsync(() -> 0); + for (ComposeSource src : sources) { + index++; + final int i = index; + completableFuture = + completableFuture.thenCombine( + statObject(new StatObjectArgs((ObjectReadArgs) src)), + (partCount, statObjectResponse) -> { + src.buildHeaders(statObjectResponse.size(), statObjectResponse.etag()); + + long size = statObjectResponse.size(); + if (src.length() != null) { + size = src.length(); + } else if (src.offset() != null) { + size -= src.offset(); + } + + if (size < ObjectWriteArgs.MIN_MULTIPART_SIZE + && sources.size() != 1 + && i != sources.size()) { + throw new IllegalArgumentException( + "source " + + src.bucket() + + "/" + + src.object() + + ": size " + + size + + " must be greater than " + + ObjectWriteArgs.MIN_MULTIPART_SIZE); + } + + objectSize[0] += size; + if (objectSize[0] > ObjectWriteArgs.MAX_OBJECT_SIZE) { + throw new IllegalArgumentException( + "destination object size must be less than " + + ObjectWriteArgs.MAX_OBJECT_SIZE); + } + + if (size > ObjectWriteArgs.MAX_PART_SIZE) { + long count = size / ObjectWriteArgs.MAX_PART_SIZE; + long lastPartSize = size - (count * ObjectWriteArgs.MAX_PART_SIZE); + if (lastPartSize > 0) { + count++; + } else { + lastPartSize = ObjectWriteArgs.MAX_PART_SIZE; + } + + if (lastPartSize < ObjectWriteArgs.MIN_MULTIPART_SIZE + && sources.size() != 1 + && i != sources.size()) { + throw new IllegalArgumentException( + "source " + + src.bucket() + + "/" + + src.object() + + ": " + + "for multipart split upload of " + + size + + ", last part size is less than " + + ObjectWriteArgs.MIN_MULTIPART_SIZE); + } + partCount += (int) count; + } else { + partCount++; + } + + if (partCount > ObjectWriteArgs.MAX_MULTIPART_COUNT) { + throw new IllegalArgumentException( + "Compose sources create more than allowed multipart count " + + ObjectWriteArgs.MAX_MULTIPART_COUNT); + } + return partCount; + }); + } + + return completableFuture; + } +} diff --git a/api/src/main/java/io/minio/BaseS3Client.java b/api/src/main/java/io/minio/BaseS3Client.java new file mode 100644 index 000000000..985332909 --- /dev/null +++ b/api/src/main/java/io/minio/BaseS3Client.java @@ -0,0 +1,1459 @@ +/* + * 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.JsonParseException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; +import io.minio.credentials.Credentials; +import io.minio.credentials.Provider; +import io.minio.errors.ErrorResponseException; +import io.minio.errors.InsufficientDataException; +import io.minio.errors.InternalException; +import io.minio.errors.InvalidResponseException; +import io.minio.errors.ServerException; +import io.minio.errors.XmlParserException; +import io.minio.messages.CompleteMultipartUpload; +import io.minio.messages.CompleteMultipartUploadResult; +import io.minio.messages.CopyPartResult; +import io.minio.messages.DeleteRequest; +import io.minio.messages.DeleteResult; +import io.minio.messages.ErrorResponse; +import io.minio.messages.InitiateMultipartUploadResult; +import io.minio.messages.Item; +import io.minio.messages.ListAllMyBucketsResult; +import io.minio.messages.ListBucketResultV1; +import io.minio.messages.ListBucketResultV2; +import io.minio.messages.ListMultipartUploadsResult; +import io.minio.messages.ListObjectsResult; +import io.minio.messages.ListPartsResult; +import io.minio.messages.ListVersionsResult; +import io.minio.messages.LocationConstraint; +import io.minio.messages.NotificationRecords; +import io.minio.messages.Part; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.RandomAccessFile; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +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; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.logging.Logger; +import javax.annotation.Nonnull; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.ResponseBody; + +/** Core S3 API client. */ +public abstract class BaseS3Client implements AutoCloseable { + static { + try { + RequestBody.create(new byte[] {}, null); + } catch (NoSuchMethodError ex) { + throw new RuntimeException("Unsupported OkHttp library found. Must use okhttp >= 4.11.0", ex); + } + } + + protected static final String NO_SUCH_BUCKET_MESSAGE = "Bucket does not exist"; + protected static final String NO_SUCH_BUCKET = "NoSuchBucket"; + protected static final String NO_SUCH_BUCKET_POLICY = "NoSuchBucketPolicy"; + protected static final String NO_SUCH_OBJECT_LOCK_CONFIGURATION = "NoSuchObjectLockConfiguration"; + protected static final String SERVER_SIDE_ENCRYPTION_CONFIGURATION_NOT_FOUND_ERROR = + "ServerSideEncryptionConfigurationNotFoundError"; + // maximum allowed bucket policy size is 20KiB + protected static final int MAX_BUCKET_POLICY_SIZE = 20 * 1024; + 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, "acl", "attributes"); + private PrintWriter traceStream; + private String userAgent = Utils.getDefaultUserAgent(); + + protected Http.BaseUrl baseUrl; + protected Provider provider; + protected OkHttpClient httpClient; + protected boolean closeHttpClient; + + protected BaseS3Client( + Http.BaseUrl baseUrl, + Provider provider, + OkHttpClient httpClient, + boolean closeHttpClient) { + this.baseUrl = baseUrl; + this.provider = provider; + this.httpClient = httpClient; + this.closeHttpClient = closeHttpClient; + } + + protected BaseS3Client(BaseS3Client client) { + this.baseUrl = client.baseUrl; + this.provider = client.provider; + this.httpClient = client.httpClient; + this.closeHttpClient = client.closeHttpClient; + } + + @Override + public void close() throws Exception { + if (closeHttpClient) { + httpClient.dispatcher().executorService().shutdown(); + httpClient.connectionPool().evictAll(); + } + } + + /** + * 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. + * + *
Example:{@code
+   * minioClient.setTimeout(TimeUnit.SECONDS.toMillis(10), TimeUnit.SECONDS.toMillis(10),
+   *     TimeUnit.SECONDS.toMillis(30));
+   * }
+ * + * @param connectTimeout HTTP connect timeout in milliseconds. + * @param writeTimeout HTTP write timeout in milliseconds. + * @param readTimeout HTTP read timeout in milliseconds. + */ + public void setTimeout(long connectTimeout, long writeTimeout, long readTimeout) { + this.httpClient = Http.setTimeout(this.httpClient, connectTimeout, writeTimeout, readTimeout); + } + + /** + * Ignores check on server certificate for HTTPS connection. + * + *
Example:{@code
+   * minioClient.ignoreCertCheck();
+   * }
+ * + * @throws KeyManagementException thrown to indicate key management error. + * @throws NoSuchAlgorithmException thrown to indicate missing of SSL library. + */ + @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( + value = "SIC", + justification = "Should not be used in production anyways.") + public void ignoreCertCheck() throws KeyManagementException, NoSuchAlgorithmException { + this.httpClient = Http.disableCertCheck(this.httpClient); + } + + /** + * Sets application's name/version to user agent. For more information about user agent refer #rfc2616. + * + * @param name Your application name. + * @param version Your application version. + */ + public void setAppInfo(String name, String version) { + if (name == null || version == null) return; + this.userAgent = Utils.getDefaultUserAgent() + " " + name.trim() + "/" + version.trim(); + } + + /** + * Enables HTTP call tracing and written to traceStream. + * + * @param traceStream {@link OutputStream} for writing HTTP call tracing. + * @see #traceOff + */ + public void traceOn(OutputStream traceStream) { + if (traceStream == null) throw new IllegalArgumentException("trace stream must be provided"); + this.traceStream = + new PrintWriter(new OutputStreamWriter(traceStream, StandardCharsets.UTF_8), true); + } + + /** + * Disables HTTP call tracing previously enabled. + * + * @see #traceOn + * @throws IOException upon connection error + */ + public void traceOff() throws IOException { + this.traceStream = null; + } + + /** Enables dual-stack endpoint for Amazon S3 endpoint. */ + public void enableDualStackEndpoint() { + baseUrl.enableDualStackEndpoint(); + } + + /** Disables dual-stack endpoint for Amazon S3 endpoint. */ + public void disableDualStackEndpoint() { + baseUrl.disableDualStackEndpoint(); + } + + /** Enables virtual-style endpoint. */ + public void enableVirtualStyleEndpoint() { + baseUrl.enableVirtualStyleEndpoint(); + } + + /** Disables virtual-style endpoint. */ + public void disableVirtualStyleEndpoint() { + baseUrl.disableVirtualStyleEndpoint(); + } + + /** Sets AWS S3 domain prefix. */ + public void setAwsS3Prefix(@Nonnull String awsS3Prefix) { + baseUrl.setAwsS3Prefix(awsS3Prefix); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////// HTTP execution methods //////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////////////////// + + private String[] handleRedirectResponse( + Http.Method method, String bucketName, Response response, boolean retry) { + String code = null; + String message = null; + + if (response.code() == 301) { + code = "PermanentRedirect"; + message = "Moved Permanently"; + } else if (response.code() == 307) { + code = "Redirect"; + message = "Temporary redirect"; + } else if (response.code() == 400) { + code = "BadRequest"; + message = "Bad request"; + } + + String region = response.headers().get("x-amz-bucket-region"); + if (message != null && region != null) message += ". Use region " + region; + + if (retry + && region != null + && method.equals(Http.Method.HEAD) + && bucketName != null + && regionCache.get(bucketName) != null) { + code = RETRY_HEAD; + message = null; + } + + return new String[] {code, message}; + } + + /** Execute HTTP request asynchronously for given parameters. */ + protected CompletableFuture executeAsync(Http.S3Request s3request, String region) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + HttpUrl url = + baseUrl.buildUrl( + s3request.method(), + s3request.bucket(), + s3request.object(), + region, + s3request.queryParams()); + Credentials credentials = (provider == null) ? null : provider.fetch(); + okhttp3.Request request = s3request.httpRequest(url, credentials); + + StringBuilder traceBuilder = new StringBuilder(s3request.traces()); + PrintWriter traceStream = this.traceStream; + if (traceStream != null) traceStream.print(s3request.traces()); + + OkHttpClient httpClient = this.httpClient; + if (!s3request.retryFailure()) { + httpClient = httpClient.newBuilder().retryOnConnectionFailure(false).build(); + } + + CompletableFuture completableFuture = new CompletableFuture<>(); + httpClient + .newCall(request) + .enqueue( + new Callback() { + @Override + public void onFailure(final Call call, IOException e) { + completableFuture.completeExceptionally(e); + } + + @Override + public void onResponse(Call call, final Response response) throws IOException { + try { + onResponse(response); + } catch (Exception e) { + completableFuture.completeExceptionally(e); + } + } + + private void onResponse(final Response response) throws IOException { + String trace = + String.format( + "%s %d %s\n%s\n\n", + response.protocol().toString().toUpperCase(Locale.US), + response.code(), + response.message(), + response.headers().toString()); + traceBuilder.append(trace); + if (traceStream != null) traceStream.print(trace); + + if (response.isSuccessful()) { + if (traceStream != null) { + // Trace response body only if the request is not + // GetObject/ListenBucketNotification + // S3 API. + Set keys = s3request.queryParams().keySet(); + if ((s3request.method() != Http.Method.GET + || s3request.object() == null + || !Collections.disjoint(keys, TRACE_QUERY_PARAMS)) + && !(keys.contains("events") + && (keys.contains("prefix") || keys.contains("suffix")))) { + ResponseBody responseBody = response.peekBody(1024 * 1024); + traceStream.println(responseBody.string()); + } + traceStream.println(END_HTTP); + } + + completableFuture.complete(response); + return; + } + + String errorXml = null; + try (ResponseBody responseBody = response.body()) { + errorXml = responseBody.string(); + } + + if (!("".equals(errorXml) && s3request.method().equals(Http.Method.HEAD))) { + traceBuilder.append(errorXml); + if (traceStream != null) traceStream.print(errorXml); + if (!errorXml.endsWith("\n")) { + traceBuilder.append("\n"); + if (traceStream != null) traceStream.println(); + } + } + traceBuilder.append(END_HTTP).append("\n"); + if (traceStream != null) traceStream.println(END_HTTP); + + // Error in case of Non-XML response from server for non-HEAD requests. + String contentType = response.headers().get("content-type"); + if (!s3request.method().equals(Http.Method.HEAD) + && (contentType == null + || !Arrays.asList(contentType.split(";")).contains("application/xml"))) { + if (response.code() == 304 && response.body().contentLength() == 0) { + completableFuture.completeExceptionally( + new ServerException( + "server failed with HTTP status code " + response.code(), + response.code(), + traceBuilder.toString())); + } + + completableFuture.completeExceptionally( + new InvalidResponseException( + response.code(), + contentType, + errorXml.substring( + 0, errorXml.length() > 1024 ? 1024 : errorXml.length()), + traceBuilder.toString())); + return; + } + + ErrorResponse errorResponse = null; + if (!"".equals(errorXml)) { + try { + errorResponse = Xml.unmarshal(ErrorResponse.class, errorXml); + } catch (XmlParserException e) { + completableFuture.completeExceptionally(e); + return; + } + } else if (!s3request.method().equals(Http.Method.HEAD)) { + completableFuture.completeExceptionally( + new InvalidResponseException( + response.code(), contentType, errorXml, traceBuilder.toString())); + return; + } + + if (errorResponse == null) { + String code = null; + String message = null; + switch (response.code()) { + case 301: + case 307: + case 400: + String[] result = + handleRedirectResponse( + s3request.method(), s3request.bucket(), response, true); + code = result[0]; + message = result[1]; + break; + case 404: + if (s3request.object() != null) { + code = "NoSuchKey"; + message = "Object does not exist"; + } else if (s3request.bucket() != null) { + code = NO_SUCH_BUCKET; + message = NO_SUCH_BUCKET_MESSAGE; + } else { + code = "ResourceNotFound"; + message = "Request resource not found"; + } + break; + case 501: + case 405: + code = "MethodNotAllowed"; + message = "The specified method is not allowed against this resource"; + break; + case 409: + if (s3request.bucket() != null) { + code = NO_SUCH_BUCKET; + message = NO_SUCH_BUCKET_MESSAGE; + } else { + code = "ResourceConflict"; + message = "Request resource conflicts"; + } + break; + case 403: + code = "AccessDenied"; + message = "Access denied"; + break; + case 412: + code = "PreconditionFailed"; + message = "At least one of the preconditions you specified did not hold"; + break; + case 416: + code = "InvalidRange"; + message = "The requested range cannot be satisfied"; + break; + default: + completableFuture.completeExceptionally( + new ServerException( + "server failed with HTTP status code " + response.code(), + response.code(), + traceBuilder.toString())); + return; + } + + errorResponse = + new ErrorResponse( + code, + message, + s3request.bucket(), + s3request.object(), + request.url().encodedPath(), + response.header("x-amz-request-id"), + response.header("x-amz-id-2")); + } + + // invalidate region cache if needed + if (errorResponse.code().equals(NO_SUCH_BUCKET) + || errorResponse.code().equals(RETRY_HEAD)) { + regionCache.remove(s3request.bucket()); + } + + ErrorResponseException e = + new ErrorResponseException(errorResponse, response, traceBuilder.toString()); + completableFuture.completeExceptionally(e); + } + }); + return completableFuture; + } + + /** Execute HTTP request asynchronously for given args and parameters. */ + protected CompletableFuture executeAsync(Http.S3Request s3request) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + return getRegion(s3request.bucket(), s3request.region()) + .thenCompose( + location -> { + try { + return executeAsync(s3request, location); + } catch (InsufficientDataException + | InternalException + | InvalidKeyException + | IOException + | NoSuchAlgorithmException + | XmlParserException e) { + throw new CompletionException(e); + } + }); + } + + /** Execute asynchronously GET HTTP request for given parameters. */ + protected CompletableFuture executeGetAsync( + BaseArgs args, Multimap headers, Multimap queryParams) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + return executeAsync( + Http.S3Request.builder() + .userAgent(userAgent) + .method(Http.Method.GET) + .headers(headers) + .queryParams(queryParams) + .baseArgs(args) + .build()); + } + + /** Execute asynchronously HEAD HTTP request for given parameters. */ + protected CompletableFuture executeHeadAsync( + BaseArgs args, Multimap headers, Multimap queryParams) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + Http.S3Request s3request = + Http.S3Request.builder() + .userAgent(userAgent) + .method(Http.Method.HEAD) + .headers(headers) + .queryParams(queryParams) + .baseArgs(args) + .build(); + return executeAsync(s3request) + .exceptionally( + e -> { + if (e instanceof ErrorResponseException) { + ErrorResponseException ex = (ErrorResponseException) e; + if (ex.errorResponse().code().equals(RETRY_HEAD)) { + return null; + } + } + throw new CompletionException(e); + }) + .thenCompose( + response -> { + if (response != null) { + return CompletableFuture.completedFuture(response); + } + + try { + return executeAsync(s3request); + } catch (InsufficientDataException + | InternalException + | InvalidKeyException + | IOException + | NoSuchAlgorithmException + | XmlParserException e) { + throw new CompletionException(e); + } + }); + } + + /** Execute asynchronously DELETE HTTP request for given parameters. */ + protected CompletableFuture executeDeleteAsync( + BaseArgs args, Multimap headers, Multimap queryParams) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + return executeAsync( + Http.S3Request.builder() + .userAgent(userAgent) + .method(Http.Method.DELETE) + .headers(headers) + .queryParams(queryParams) + .baseArgs(args) + .build()) + .thenApply( + response -> { + if (response != null) response.body().close(); + return response; + }); + } + + /** Execute asynchronously POST HTTP request for given parameters. */ + protected CompletableFuture executePostAsync( + BaseArgs args, + Multimap headers, + Multimap queryParams, + Object data) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + return executeAsync( + Http.S3Request.builder() + .userAgent(userAgent) + .method(Http.Method.POST) + .headers(headers) + .queryParams(queryParams) + .body(data, null, null, null, null) + .baseArgs(args) + .build()); + } + + /** Execute asynchronously PUT HTTP request for given parameters. */ + protected CompletableFuture executePutAsync( + BaseArgs args, + Multimap headers, + Multimap queryParams, + Object data, + int length) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + return executeAsync( + Http.S3Request.builder() + .userAgent(userAgent) + .method(Http.Method.PUT) + .headers(headers) + .queryParams(queryParams) + .body(data, (long) length, null, null, null) + .baseArgs(args) + .build()); + } + + /** Returns region of given bucket either from region cache or set in constructor. */ + protected CompletableFuture getRegion(String bucket, String region) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + String thisRegion = this.baseUrl.region(); + if (region != null) { + // Error out if region does not match with region passed via constructor. + if (thisRegion != null && !thisRegion.equals(region)) { + throw new IllegalArgumentException( + "region must be " + thisRegion + ", but passed " + region); + } + return CompletableFuture.completedFuture(region); + } + + if (thisRegion != null && !thisRegion.equals("")) { + return CompletableFuture.completedFuture(thisRegion); + } + if (bucket == null || this.provider == null) { + return CompletableFuture.completedFuture(Http.US_EAST_1); + } + region = regionCache.get(bucket); + if (region != null) return CompletableFuture.completedFuture(region); + + return getBucketLocation(GetBucketLocationArgs.builder().bucket(bucket).build()); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////// S3 APIs and their helpers are added here /////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////////////////// + + /** Check whether argument is valid or not. */ + protected void checkArgs(BaseArgs args) { + if (args == null) throw new IllegalArgumentException("null arguments"); + + if ((baseUrl.awsDomainSuffix() != null) && (args instanceof BucketArgs)) { + String bucketName = ((BucketArgs) args).bucket(); + if (bucketName.startsWith("xn--") + || bucketName.endsWith("--s3alias") + || bucketName.endsWith("--ol-s3")) { + throw new IllegalArgumentException( + "bucket name '" + + bucketName + + "' must not start with 'xn--' and must not end with '--s3alias' or '--ol-s3'"); + } + } + } + + /** + * Do AbortMultipartUpload + * S3 API asynchronously. + * + * @param args {@link AbortMultipartUploadArgs} object. + * @return {@link CompletableFuture}<{@link AbortMultipartUploadResponse}> 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 abortMultipartUpload( + AbortMultipartUploadArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executeDeleteAsync( + args, + null, + Utils.mergeMultimap( + args.extraQueryParams(), Utils.newMultimap(UPLOAD_ID, args.uploadId()))) + .thenApply( + response -> { + try { + return new AbortMultipartUploadResponse( + response.headers(), args.bucket(), region, args.object(), args.uploadId()); + } finally { + response.close(); + } + }); + } + + /** + * Do CompleteMultipartUpload + * S3 API asynchronously. + * + * @param args {@link CompleteMultipartUploadArgs} object. + * @return {@link CompletableFuture}<{@link ObjectWriteResponse}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture completeMultipartUpload( + CompleteMultipartUploadArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executePostAsync( + args, + null, + Utils.mergeMultimap( + args.extraQueryParams(), Utils.newMultimap(UPLOAD_ID, args.uploadId())), + new CompleteMultipartUpload(args.parts())) + .thenApply( + response -> { + try { + String bodyContent = response.body().string(); + bodyContent = bodyContent.trim(); + if (!bodyContent.isEmpty()) { + try { + if (Xml.validate(ErrorResponse.class, bodyContent)) { + ErrorResponse errorResponse = Xml.unmarshal(ErrorResponse.class, bodyContent); + throw new CompletionException( + new ErrorResponseException(errorResponse, response, null)); + } + } catch (XmlParserException e) { + // As it is not message, fallback to parse CompleteMultipartUploadOutput + // XML. + } + + try { + CompleteMultipartUploadResult result = + Xml.unmarshal(CompleteMultipartUploadResult.class, bodyContent); + return new ObjectWriteResponse( + response.headers(), + result.bucket(), + result.location(), + result.object(), + result.etag(), + response.header("x-amz-version-id"), + result); + } catch (XmlParserException e) { + // As this CompleteMultipartUpload REST call succeeded, just log it. + Logger.getLogger(S3Base.class.getName()) + .warning( + "S3 service returned unknown XML for CompleteMultipartUpload REST API. " + + bodyContent); + } + } + + return new ObjectWriteResponse( + response.headers(), + args.bucket(), + region, + args.object(), + null, + response.header("x-amz-version-id")); + } catch (IOException e) { + throw new CompletionException(e); + } finally { + response.close(); + } + }); + } + + /** + * Do CreateMultipartUpload + * S3 API asynchronously. + * + * @param args {@link CreateMultipartUploadArgs} object. + * @return {@link CompletableFuture}<{@link CreateMultipartUploadResponse}> 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 createMultipartUpload( + CreateMultipartUploadArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executePostAsync( + args, + null, + Utils.mergeMultimap(args.extraQueryParams(), Utils.newMultimap("uploads", "")), + null) + .thenApply( + response -> { + try { + InitiateMultipartUploadResult result = + Xml.unmarshal( + InitiateMultipartUploadResult.class, response.body().charStream()); + return new CreateMultipartUploadResponse( + response.headers(), args.bucket(), region, args.object(), result); + } catch (XmlParserException e) { + throw new CompletionException(e); + } finally { + response.close(); + } + }); + } + + /** + * Do DeleteObjects S3 + * API asynchronously. + * + * @param args {@link DeleteObjectsArgs} object. + * @return {@link CompletableFuture}<{@link DeleteObjectsResponse}> 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 deleteObjects(DeleteObjectsArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executePostAsync( + args, + Utils.mergeMultimap( + args.extraHeaders(), + args.bypassGovernanceMode() + ? Utils.newMultimap("x-amz-bypass-governance-retention", "true") + : null), + Utils.mergeMultimap(args.extraQueryParams(), Utils.newMultimap("delete", "")), + new DeleteRequest(args.objects(), args.quiet())) + .thenApply( + response -> { + try { + String bodyContent = response.body().string(); + try { + if (Xml.validate(DeleteResult.Error.class, bodyContent)) { + DeleteResult.Error error = Xml.unmarshal(DeleteResult.Error.class, bodyContent); + DeleteResult result = new DeleteResult(error); + return new DeleteObjectsResponse( + response.headers(), args.bucket(), args.region(), result); + } + } catch (XmlParserException e) { + // Ignore this exception as it is not message, + // but parse it as message below. + } + + DeleteResult result = Xml.unmarshal(DeleteResult.class, bodyContent); + return new DeleteObjectsResponse( + response.headers(), args.bucket(), args.region(), result); + } catch (IOException | XmlParserException e) { + throw new CompletionException(e); + } finally { + response.close(); + } + }); + } + + /** + * Do GetBucketLocation S3 + * API asynchronously. + * + * @param args {@link GetBucketLocationArgs} object. + * @return {@link CompletableFuture}<{@link String}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture getBucketLocation(GetBucketLocationArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executeAsync( + Http.S3Request.builder() + .userAgent(userAgent) + .method(Http.Method.GET) + .baseArgs(args) + .queryParams(Utils.newMultimap("location", null)) + .build(), + Http.US_EAST_1) + .thenApply( + response -> { + String location; + try (ResponseBody body = response.body()) { + LocationConstraint lc = Xml.unmarshal(LocationConstraint.class, body.charStream()); + if (lc.location() == null || lc.location().equals("")) { + location = Http.US_EAST_1; + } else if (lc.location().equals("EU") && this.awsDomainSuffix != null) { + location = "eu-west-1"; // eu-west-1 is also referred as 'EU'. + } else { + location = lc.location(); + } + } catch (XmlParserException e) { + throw new CompletionException(e); + } + + regionCache.put(args.bucket(), location); + return location; + }); + } + + /** + * Do HeadObject + * S3 API asynchronously. + * + * @param args {@link HeadObjectArgs} object. + * @return {@link CompletableFuture}<{@link HeadObjectResponse}> 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 headObject(HeadObjectArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + args.validateSsec(baseUrl); + return executeHeadAsync( + args, + args.getHeaders(), + (args.versionId() != null) ? Utils.newMultimap("versionId", args.versionId()) : null) + .thenApply( + response -> + new HeadObjectResponse( + response.headers(), args.bucket(), args.region(), args.object())); + } + + /** + * Do ListBuckets + * S3 API asynchronously. + * + * @param args {@link ListBucketsArgs} object. + * @return {@link CompletableFuture}<{@link ListBucketsResponse}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture listBuckets(ListBucketsArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + + Multimap queryParams = Utils.newMultimap(args.extraQueryParams()); + if (args.bucketRegion() != null) queryParams.put("bucket-region", args.bucketRegion()); + queryParams.put( + "max-buckets", Integer.toString(args.maxBuckets() > 0 ? args.maxBuckets() : 10000)); + if (args.prefix() != null) queryParams.put("prefix", args.prefix()); + if (args.continuationToken() != null) { + queryParams.put("continuation-token", args.continuationToken()); + } + + return executeGetAsync(args, args.extraHeaders(), queryParams) + .thenApply( + response -> { + try { + ListAllMyBucketsResult result = + Xml.unmarshal(ListAllMyBucketsResult.class, response.body().charStream()); + return new ListBucketsResponse(response.headers(), result); + } catch (XmlParserException e) { + throw new CompletionException(e); + } finally { + response.close(); + } + }); + } + + /** + * Do ListMultipartUploads + * S3 API asynchronously. + * + * @param args {@link ListMultipartUploadsArgs} object. + * @return {@link CompletableFuture}<{@link ListMultipartUploadsResponse}> 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 listMultipartUploads( + ListMultipartUploadsArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + Multimap queryParams = + Utils.mergeMultimap( + args.extraQueryParams(), + Utils.newMultimap( + "uploads", + "", + "delimiter", + (args.delimiter() != null) ? args.delimiter() : "", + "max-uploads", + (args.maxUploads() != null) ? args.maxUploads().toString() : "1000", + "prefix", + (args.prefix() != null) ? args.prefix() : "")); + if (args.encodingType() != null) queryParams.put("encoding-type", args.encodingType()); + if (args.keyMarker() != null) queryParams.put("key-marker", args.keyMarker()); + if (args.uploadIdMarker() != null) queryParams.put("upload-id-marker", args.uploadIdMarker()); + + return executeGetAsync(args, args.extraHeaders(), queryParams) + .thenApply( + response -> { + try { + ListMultipartUploadsResult result = + Xml.unmarshal(ListMultipartUploadsResult.class, response.body().charStream()); + return new ListMultipartUploadsResponse( + response.headers(), args.bucket(), args.region(), result); + } catch (XmlParserException e) { + throw new CompletionException(e); + } finally { + response.close(); + } + }); + } + + private Multimap getCommonListObjectsQueryParams( + String delimiter, String encodingType, Integer maxKeys, String prefix) { + Multimap queryParams = + Utils.newMultimap( + "delimiter", + (delimiter == null) ? "" : delimiter, + "max-keys", + Integer.toString(maxKeys > 0 ? maxKeys : 1000), + "prefix", + (prefix == null) ? "" : prefix); + if (encodingType != null) queryParams.put("encoding-type", encodingType); + return queryParams; + } + + /** + * Do ListObjects + * version 1 S3 API asynchronously. + * + * @param args {@link ListObjectsV1Args} object. + * @return {@link CompletableFuture}<{@link ListObjectsV1Response}> 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 listObjectsV1(ListObjectsV1Args args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + + Multimap queryParams = + Utils.mergeMultimap( + args.extraQueryParams(), + getCommonListObjectsQueryParams( + args.delimiter(), args.encodingType(), args.maxKeys(), args.prefix())); + if (args.marker() != null) queryParams.put("marker", args.marker()); + + return executeGetAsync(args, args.extraHeaders(), queryParams) + .thenApply( + response -> { + try { + ListBucketResultV1 result = + Xml.unmarshal(ListBucketResultV1.class, response.body().charStream()); + return new ListObjectsV1Response( + response.headers(), args.bucket(), args.region(), result); + } catch (XmlParserException e) { + throw new CompletionException(e); + } finally { + response.close(); + } + }); + } + + /** + * Do ListObjects + * version 2 S3 API asynchronously. + * + * @param args {@link ListObjectsV2Args} object. + * @return {@link CompletableFuture}<{@link ListObjectsV2Response}> 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 listObjectsV2(ListObjectsV2Args args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + + Multimap queryParams = + Utils.mergeMultimap( + args.extraQueryParams(), + getCommonListObjectsQueryParams( + args.delimiter(), args.encodingType(), args.maxKeys(), args.prefix())); + if (args.startAfter() != null) queryParams.put("start-after", args.startAfter()); + if (args.continuationToken() != null) + queryParams.put("continuation-token", args.continuationToken()); + if (args.fetchOwner()) queryParams.put("fetch-owner", "true"); + if (args.includeUserMetadata()) queryParams.put("metadata", "true"); + queryParams.put("list-type", "2"); + + return executeGetAsync(args, args.extraHeaders(), queryParams) + .thenApply( + response -> { + try { + ListBucketResultV2 result = + Xml.unmarshal(ListBucketResultV2.class, response.body().charStream()); + return new ListObjectsV2Response( + response.headers(), args.bucket(), args.region(), result); + } catch (XmlParserException e) { + throw new CompletionException(e); + } finally { + response.close(); + } + }); + } + + /** + * Do ListObjectVersions + * API asynchronously. + * + * @param args {@link ListObjectVersionsArgs} object. + * @return {@link CompletableFuture}<{@link ListObjectVersionsResponse}> 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 listObjectVersions( + ListObjectVersionsArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + + Multimap queryParams = + Utils.mergeMultimap( + args.extraQueryParams(), + getCommonListObjectsQueryParams( + args.delimiter(), args.encodingType(), args.maxKeys(), args.prefix())); + if (args.keyMarker() != null) queryParams.put("key-marker", args.keyMarker()); + if (args.versionIdMarker() != null) { + queryParams.put("version-id-marker", args.versionIdMarker()); + } + queryParams.put("versions", ""); + + return executeGetAsync(args, args.extraHeaders(), queryParams) + .thenApply( + response -> { + try { + ListVersionsResult result = + Xml.unmarshal(ListVersionsResult.class, response.body().charStream()); + return new ListObjectVersionsResponse( + response.headers(), args.bucket(), args.region(), result); + } catch (XmlParserException e) { + throw new CompletionException(e); + } finally { + response.close(); + } + }); + } + + /** + * Do ListParts S3 + * API asynchronously. + * + * @param args {@link ListPartsArgs} object. + * @return {@link CompletableFuture}<{@link ListPartsResponse}> 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 listParts(ListPartsArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + Multimap queryParams = + Utils.mergeMultimap( + args.extraQueryParams(), + Utils.newMultimap( + UPLOAD_ID, + args.uploadId(), + "max-parts", + (args.maxParts() != null) ? args.maxParts().toString() : "1000")); + if (args.partNumberMarker() != null) { + queryParams.put("part-number-marker", args.partNumberMarker().toString()); + } + + return executeGetAsync(args, args.extraHeaders(), queryParams) + .thenApply( + response -> { + try { + ListPartsResult result = + Xml.unmarshal(ListPartsResult.class, response.body().charStream()); + return new ListPartsResponse( + response.headers(), args.bucket(), args.region(), args.object(), result); + } catch (XmlParserException e) { + throw new CompletionException(e); + } finally { + response.close(); + } + }); + } + + // /** + // * Execute put object asynchronously from object data from {@link RandomAccessFile} or {@link + // * InputStream}. + // * + // * @param args {@link PutObjectBaseArgs}. + // * @param data {@link RandomAccessFile} or {@link InputStream}. + // * @param objectSize object size. + // * @param partSize part size for multipart upload. + // * @param partCount Number of parts for multipart upload. + // * @param contentType content-type of object. + // * @return {@link CompletableFuture}<{@link ObjectWriteResponse}> object. + // * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + // * @throws InternalException thrown to indicate internal library error. + // * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + // * @throws IOException thrown to indicate I/O error on S3 operation. + // * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + // * @throws XmlParserException thrown to indicate XML parsing error. + // */ + // protected CompletableFuture putObjectAsync( + // PutObjectBaseArgs args, + // Object data, + // long objectSize, + // long partSize, + // int partCount, + // String contentType) + // throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + // NoSuchAlgorithmException, XmlParserException { + // PartReader partReader = newPartReader(data, objectSize, partSize, partCount); + // if (partReader == null) { + // throw new IllegalArgumentException("data must be RandomAccessFile or InputStream"); + // } + // + // Multimap headers = Utils.newMultimap(args.extraHeaders()); + // headers.putAll(args.genHeaders()); + // if (!headers.containsKey("Content-Type")) headers.put("Content-Type", contentType); + // + // return CompletableFuture.supplyAsync( + // () -> { + // try { + // return partReader.getPart(); + // } catch (NoSuchAlgorithmException | IOException e) { + // throw new CompletionException(e); + // } + // }) + // .thenCompose( + // partSource -> { + // try { + // if (partReader.partCount() == 1) { + // return putObjectAsync( + // args.bucket(), + // args.region(), + // args.object(), + // partSource, + // headers, + // args.extraQueryParams()); + // } else { + // return putMultipartObjectAsync(args, headers, partReader, partSource); + // } + // } catch (InsufficientDataException + // | InternalException + // | InvalidKeyException + // | IOException + // | NoSuchAlgorithmException + // | XmlParserException e) { + // throw new CompletionException(e); + // } + // }); + // } + + /** + * Do PutObject S3 + * API asynchronously. + * + * @param args {@link PutObjectAPIArgs} object. + * @return {@link CompletableFuture}<{@link ObjectWriteResponse}> object. + * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. + * @throws InternalException thrown to indicate internal library error. + * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. + * @throws IOException thrown to indicate I/O error on S3 operation. + * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. + * @throws XmlParserException thrown to indicate XML parsing error. + */ + public CompletableFuture putObject(PutObjectAPIArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + Long length = args.length(); + Object data = args.file(); + if (args.file() == null) data = args.buffer(); + if (args.buffer() == null) data = args.data(); + return executePutAsync( + args, + Utils.mergeMultimap(args.extraHeaders(), args.headers()), + args.extraQueryParams(), + data, + length) + .thenApply( + response -> { + try { + return new ObjectWriteResponse( + response.headers(), + args.bucket(), + args.region(), + args.object(), + response.header("ETag").replaceAll("\"", ""), + response.header("x-amz-version-id")); + } finally { + response.close(); + } + }); + } + + /** + * Do UploadPart S3 + * API asynchronously. + * + * @param args {@link UploadPartArgs} object. + * @return {@link CompletableFuture}<{@link UploadPartResponse}> 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 uploadPart(UploadPartArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + Long length = args.length(); + Object data = args.file(); + if (args.file() == null) data = args.buffer(); + if (args.buffer() == null) data = args.data(); + return executePutAsync( + args, + args.extraHeaders(), + Utils.mergeMultimap( + args.extraQueryParams(), + Utils.newMultimap( + "partNumber", + Integer.toString(args.partNumber()), + "uploadId", + args.uploadId())), + data, + length) + .thenApply( + response -> { + try { + return new UploadPartResponse( + response.headers(), + args.bucket(), + args.region(), + args.object(), + args.uploadId(), + args.partNumber(), + response.header("ETag").replaceAll("\"", "")); + } finally { + response.close(); + } + }); + } + + /** + * Do UploadPartCopy + * S3 API. + * + * @param args {@link UploadPartCopyArgs} object. + * @return {@link CompletableFuture}<{@link UploadPartCopyResponse}> 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 uploadPartCopy(UploadPartCopyArgs args) + throws InsufficientDataException, InternalException, InvalidKeyException, IOException, + NoSuchAlgorithmException, XmlParserException { + checkArgs(args); + return executePutAsync( + args, + Utils.mergeMultimap(args.extraHeaders(), args.headers()), + Utils.mergeMultimap( + args.extraQueryParams(), + Utils.newMultimap( + "partNumber", + Integer.toString(args.partNumber()), + "uploadId", + args.uploadId())), + null, + 0) + .thenApply( + response -> { + try { + CopyPartResult result = + Xml.unmarshal(CopyPartResult.class, response.body().charStream()); + return new UploadPartCopyResponse( + response.headers(), + args.bucket(), + args.region(), + args.object(), + args.uploadId(), + args.partNumber(), + result); + } catch (XmlParserException e) { + throw new CompletionException(e); + } finally { + response.close(); + } + }); + } +} diff --git a/api/src/main/java/io/minio/BucketArgs.java b/api/src/main/java/io/minio/BucketArgs.java index c50bcdfda..ef902729b 100644 --- a/api/src/main/java/io/minio/BucketArgs.java +++ b/api/src/main/java/io/minio/BucketArgs.java @@ -16,8 +16,6 @@ package io.minio; -import io.minio.http.HttpUtils; -import io.minio.org.apache.commons.validator.routines.InetAddressValidator; import java.util.Objects; import java.util.regex.Pattern; @@ -42,7 +40,7 @@ public abstract static class Builder, A extends BucketAr protected boolean skipValidation = false; protected void validateBucketName(String name) { - validateNotNull(name, "bucket name"); + Utils.validateNotNull(name, "bucket name"); if (skipValidation) { return; } @@ -55,7 +53,7 @@ protected void validateBucketName(String name) { + "https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html"); } - if (InetAddressValidator.getInstance().isValidInet4Address(name)) { + if (Utils.isValidIPv4(name)) { throw new IllegalArgumentException( "bucket name '" + name + "' must not be formatted as an IP address"); } @@ -67,7 +65,7 @@ protected void validateBucketName(String name) { } private void validateRegion(String region) { - if (!skipValidation && region != null && !HttpUtils.REGION_REGEX.matcher(region).find()) { + if (!skipValidation && region != null && !Utils.REGION_REGEX.matcher(region).find()) { throw new IllegalArgumentException("invalid region " + region); } } diff --git a/api/src/main/java/io/minio/ByteBuffer.java b/api/src/main/java/io/minio/ByteBuffer.java new file mode 100644 index 000000000..639b3921c --- /dev/null +++ b/api/src/main/java/io/minio/ByteBuffer.java @@ -0,0 +1,124 @@ +/* + * 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.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.SequenceInputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class ByteBuffer extends OutputStream { + private static class Buffer extends ByteArrayOutputStream { + public InputStream inputStream() { + return (count > 0) ? new ByteArrayInputStream(buf, 0, count) : null; + } + } + + private static final long MAX_SIZE = 5L * 1024 * 1024 * 1024; + private static final int CHUNK_SIZE = Integer.MAX_VALUE; + private final long totalSize; + private final List buffers = new ArrayList<>(); + private int index = 0; + private long writtenBytes = 0; + private boolean isClosed = false; + + public ByteBuffer(long totalSize) { + if (totalSize > MAX_SIZE) { + throw new IllegalArgumentException("Total size cannot exceed 5GiB"); + } + this.totalSize = totalSize; + } + + private void updateIndex() { + if (buffers.isEmpty()) { + index = 0; + buffers.add(new Buffer()); + } else if (writtenBytes >= (long) (index + 1) * CHUNK_SIZE) { + index++; + if (index > buffers.size() - 1) buffers.add(new Buffer()); + } + } + + @Override + public void write(int b) throws IOException { + if (isClosed) throw new IOException("Stream is closed"); + if (writtenBytes >= totalSize) throw new IOException("Exceeded total size limit"); + updateIndex(); + buffers.get(index).write(b); + writtenBytes++; + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (isClosed) throw new IOException("Stream is closed"); + if (len > (totalSize - writtenBytes)) throw new IOException("Exceeded total size limit"); + int remaining = len; + while (remaining > 0) { + updateIndex(); + Buffer currentBuffer = buffers.get(index); + int bytesToWrite = Math.min(remaining, CHUNK_SIZE - currentBuffer.size()); + currentBuffer.write(b, off + (len - remaining), bytesToWrite); + writtenBytes += bytesToWrite; + remaining -= bytesToWrite; + } + } + + @Override + public void write(byte[] b) throws IOException { + write(b, 0, b.length); + } + + public long length() { + return writtenBytes; + } + + public void reset() throws IOException { + if (isClosed) throw new IOException("Cannot reset a closed stream"); + writtenBytes = 0; + index = 0; + for (Buffer buffer : buffers) buffer.reset(); + } + + public void close() throws IOException { + if (!isClosed) { + isClosed = true; + buffers.clear(); + } + } + + public InputStream inputStream() throws IOException { + List streams = new ArrayList<>(); + for (Buffer buffer : buffers) { + InputStream stream = buffer.inputStream(); + if (stream != null) streams.add(stream); + } + switch (streams.size()) { + case 0: + return new ByteArrayInputStream(Utils.EMPTY_BODY); + case 1: + return streams.get(0); + default: + return new SequenceInputStream(Collections.enumeration(streams)); + } + } +} diff --git a/api/src/main/java/io/minio/Checksum.java b/api/src/main/java/io/minio/Checksum.java new file mode 100644 index 000000000..093b02f2c --- /dev/null +++ b/api/src/main/java/io/minio/Checksum.java @@ -0,0 +1,405 @@ +/* + * 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.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; +import java.util.Locale; + +/** Collection of checksum algorithms. */ +public class Checksum { + // MD5 hash of zero length byte array. + public static final String ZERO_MD5_HASH = "1B2M2Y8AsgTpgAmY7PhCfg=="; + // SHA-256 hash of zero length byte array. + public static final String ZERO_SHA256_HASH = + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; + + public static String base64String(byte[] sum) { + return Base64.getEncoder().encodeToString(sum); + } + + public static String hexString(byte[] sum) { + StringBuilder builder = new StringBuilder(); + for (byte b : sum) builder.append(String.format("%02x", b)); + return builder.toString(); + } + + // /** Returns MD5 hash of byte array. */ + // public static String md5Hash(byte[] data, int length) throws NoSuchAlgorithmException { + // MessageDigest md5Digest = MessageDigest.getInstance("MD5"); + // md5Digest.update(data, 0, length); + // return Base64.getEncoder().encodeToString(md5Digest.digest()); + // } + // + // /** Returns SHA-256 hash of byte array. */ + // public static String sha256Hash(byte[] data, int length) throws NoSuchAlgorithmException { + // MessageDigest sha256Digest = MessageDigest.getInstance("SHA-256"); + // sha256Digest.update((byte[]) data, 0, length); + // return BaseEncoding.base16().encode(sha256Digest.digest()).toLowerCase(Locale.US); + // } + // + // /** Returns SHA-256 hash of given string. */ + // public static String sha256Hash(String string) throws NoSuchAlgorithmException { + // byte[] data = string.getBytes(StandardCharsets.UTF_8); + // return sha256Hash(data, data.length); + // } + + /** Algorithm type. */ + public static enum Type { + COMPOSITE, + FULL_OBJECT; + } + + /** Checksum algorithm. */ + public static enum Algorithm { + CRC32, + CRC32C, + CRC64NVME, + SHA1, + SHA256, + MD5; + + public String header() { + if (this == MD5) return "Content-MD5"; + return ("x-amz-checksum-" + this).toLowerCase(Locale.US); + } + + public boolean compositeSupport() { + return this != CRC64NVME && this != MD5; + } + + public Hasher hasher() throws NoSuchAlgorithmException { + if (this == CRC32) return new CRC32(); + if (this == CRC32C) return new CRC32C(); + if (this == CRC64NVME) return new CRC64NVME(); + if (this == SHA1) return new SHA1(); + if (this == SHA256) return new SHA256(); + if (this == MD5) return new MD5(); + return null; + } + } + + public abstract static class Hasher extends OutputStream { + public abstract void update(byte[] b, int off, int len); + + public abstract byte[] sum(); + + public void update(byte[] b) { + update(b, 0, b.length); + } + + @Override + public void write(int b) { + update(new byte[] {(byte) b}); + } + + @Override + public void write(byte[] b) { + update(b); + } + + @Override + public void write(byte[] b, int off, int len) { + update(b, off, len); + } + } + + /** CRC32 checksum is java.util.zip.CRC32 compatible to Hasher. */ + public static class CRC32 extends Hasher { + private java.util.zip.CRC32 hasher; + + public CRC32() { + hasher = new java.util.zip.CRC32(); + } + + @Override + public void update(byte[] b, int off, int len) { + hasher.update(b, off, len); + } + + @Override + public void update(byte[] b) { + hasher.update(b); + } + + @Override + public byte[] sum() { + int value = (int) hasher.getValue(); + return new byte[] { + (byte) (value >>> 24), (byte) (value >>> 16), (byte) (value >>> 8), (byte) value + }; + } + + @Override + public String toString() { + return "CRC32{" + hexString(sum()) + "}"; + } + } + + /** CRC32C checksum. */ + public static class CRC32C extends Hasher implements java.util.zip.Checksum { + private static final int[] CRC32C_TABLE = new int[256]; + private int crc = 0xFFFFFFFF; + + static { + for (int i = 0; i < 256; i++) { + int crc = i; + for (int j = 0; j < 8; j++) { + crc = (crc >>> 1) ^ ((crc & 1) != 0 ? 0x82F63B78 : 0); + } + CRC32C_TABLE[i] = crc; + } + } + + @Override + public void update(int b) { + crc = CRC32C_TABLE[(crc ^ b) & 0xFF] ^ (crc >>> 8); + } + + @Override + public void update(byte[] b, int off, int len) { + for (int i = off; i < off + len; i++) { + update(b[i]); + } + } + + @Override + public long getValue() { + return (crc ^ 0xFFFFFFFFL) & 0xFFFFFFFFL; + } + + @Override + public void reset() { + crc = 0xFFFFFFFF; + } + + @Override + public byte[] sum() { + int value = (int) this.getValue(); + return new byte[] { + (byte) (value >>> 24), (byte) (value >>> 16), (byte) (value >>> 8), (byte) value + }; + } + + @Override + public String toString() { + return "CRC32C{" + hexString(sum()) + "}"; + } + } + + /** CRC64NVME checksum logic copied from https://github.com/minio/crc64nvme. */ + public static class CRC64NVME extends Hasher implements java.util.zip.Checksum { + private static final long[] CRC64_TABLE = new long[256]; + private static final long[][] SLICING8_TABLE_NVME = new long[8][256]; + + static { + long polynomial = 0x9A6C9329AC4BC9B5L; + for (int i = 0; i < 256; i++) { + long crc = i; + for (int j = 0; j < 8; j++) { + if ((crc & 1) == 1) { + crc = (crc >>> 1) ^ polynomial; + } else { + crc >>>= 1; + } + } + CRC64_TABLE[i] = crc; + } + + SLICING8_TABLE_NVME[0] = CRC64_TABLE; + for (int i = 0; i < 256; i++) { + long crc = CRC64_TABLE[i]; + for (int j = 1; j < 8; j++) { + crc = CRC64_TABLE[(int) crc & 0xFF] ^ (crc >>> 8); + SLICING8_TABLE_NVME[j][i] = crc; + } + } + } + + private long crc = 0; + + public CRC64NVME() {} + + @Override + public void update(byte[] p, int off, int len) { + ByteBuffer byteBuffer = ByteBuffer.wrap(p, off, len); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + int offset = byteBuffer.position(); + + crc = ~crc; + while (p.length >= 64 && (p.length - offset) > 8) { + long value = byteBuffer.getLong(); + crc ^= value; + crc = + SLICING8_TABLE_NVME[7][(int) (crc & 0xFF)] + ^ SLICING8_TABLE_NVME[6][(int) ((crc >>> 8) & 0xFF)] + ^ SLICING8_TABLE_NVME[5][(int) ((crc >>> 16) & 0xFF)] + ^ SLICING8_TABLE_NVME[4][(int) ((crc >>> 24) & 0xFF)] + ^ SLICING8_TABLE_NVME[3][(int) ((crc >>> 32) & 0xFF)] + ^ SLICING8_TABLE_NVME[2][(int) ((crc >>> 40) & 0xFF)] + ^ SLICING8_TABLE_NVME[1][(int) ((crc >>> 48) & 0xFF)] + ^ SLICING8_TABLE_NVME[0][(int) (crc >>> 56)]; + offset = byteBuffer.position(); + } + + for (; offset < len; offset++) { + crc = CRC64_TABLE[(int) ((crc ^ (long) p[offset]) & 0xFF)] ^ (crc >>> 8); + } + + crc = ~crc; + } + + @Override + public void update(int b) { + update(new byte[] {(byte) b}, 0, 1); + } + + @Override + public long getValue() { + return crc; + } + + @Override + public void reset() { + crc = 0; + } + + @Override + public byte[] sum() { + long value = this.getValue(); + return new byte[] { + (byte) (value >>> 56), + (byte) (value >>> 48), + (byte) (value >>> 40), + (byte) (value >>> 32), + (byte) (value >>> 24), + (byte) (value >>> 16), + (byte) (value >>> 8), + (byte) value + }; + } + + @Override + public String toString() { + return "CRC64NVME{" + hexString(sum()) + "}"; + } + } + + public static class SHA1 extends Hasher { + MessageDigest hasher; + + public SHA1() throws NoSuchAlgorithmException { + this.hasher = MessageDigest.getInstance("SHA-1"); + } + + public void update(byte[] b, int off, int len) { + hasher.update(b, off, len); + } + + public void update(byte[] b) { + hasher.update(b); + } + + public byte[] sum() { + return hasher.digest(); + } + + @Override + public String toString() { + return "SHA1{" + hexString(sum()) + "}"; + } + } + + public static class SHA256 extends Hasher { + MessageDigest hasher; + + public SHA256() throws NoSuchAlgorithmException { + this.hasher = MessageDigest.getInstance("SHA-256"); + } + + public void update(byte[] b, int off, int len) { + hasher.update(b, off, len); + } + + public void update(byte[] b) { + hasher.update(b); + } + + public byte[] sum() { + return hasher.digest(); + } + + @Override + public String toString() { + return "SHA256{" + hexString(sum()) + "}"; + } + + public static byte[] sum(byte[] b, int off, int len) throws NoSuchAlgorithmException { + SHA256 sha256 = new SHA256(); + sha256.update(b, off, len); + return sha256.sum(); + } + + public static byte[] sum(byte[] b) throws NoSuchAlgorithmException { + return sum(b, 0, b.length); + } + + public static byte[] sum(String value) throws NoSuchAlgorithmException { + return sum(value.getBytes(StandardCharsets.UTF_8)); + } + } + + public static class MD5 extends Hasher { + MessageDigest hasher; + + public MD5() throws NoSuchAlgorithmException { + this.hasher = MessageDigest.getInstance("MD5"); + } + + public void update(byte[] b, int off, int len) { + hasher.update(b, off, len); + } + + public void update(byte[] b) { + hasher.update(b); + } + + public byte[] sum() { + return hasher.digest(); + } + + @Override + public String toString() { + return "MD5{" + base64String(sum()) + "}"; + } + + public static byte[] sum(byte[] b, int off, int len) throws NoSuchAlgorithmException { + MD5 md5 = new MD5(); + md5.update(b, off, len); + return md5.sum(); + } + + public static byte[] sum(byte[] b) throws NoSuchAlgorithmException { + return sum(b, 0, b.length); + } + } +} diff --git a/api/src/main/java/io/minio/CompleteMultipartUploadArgs.java b/api/src/main/java/io/minio/CompleteMultipartUploadArgs.java new file mode 100644 index 000000000..02c182f36 --- /dev/null +++ b/api/src/main/java/io/minio/CompleteMultipartUploadArgs.java @@ -0,0 +1,78 @@ +/* + * 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.Part; +import java.util.Objects; + +/** + * Argument class of {@link MinioAsyncClient#completeMultipartUpload} and {@link + * MinioClient#completeMultipartUpload}. + */ +public class CompleteMultipartUploadArgs extends ObjectArgs { + private String uploadId; + private Part[] parts; + + public String uploadId() { + return uploadId; + } + + public Part[] parts() { + return parts; + } + + public static Builder builder() { + return new Builder(); + } + + /** Argument builder of {@link CompleteMultipartUploadArgs}. */ + public static final class Builder + extends ObjectArgs.Builder { + @Override + protected void validate(CompleteMultipartUploadArgs args) { + super.validate(args); + Utils.validateNotEmptyString(args.uploadId, "upload ID"); + Utils.validateNotNull(args.parts, "parts"); + } + + public Builder uploadId(String uploadId) { + Utils.validateNotEmptyString(uploadId, "upload ID"); + operations.add(args -> args.uploadId = uploadId); + return this; + } + + public Builder parts(Part[] parts) { + Utils.validateNotNull(parts, "parts"); + operations.add(args -> args.parts = parts); + return this; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof CompleteMultipartUploadArgs)) return false; + if (!super.equals(o)) return false; + CompleteMultipartUploadArgs that = (CompleteMultipartUploadArgs) o; + return Objects.equals(uploadId, that.uploadId) && Objects.equals(parts, that.parts); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), uploadId, parts); + } +} diff --git a/api/src/main/java/io/minio/CopyObjectArgs.java b/api/src/main/java/io/minio/CopyObjectArgs.java index 2108ab531..080ad7ca4 100644 --- a/api/src/main/java/io/minio/CopyObjectArgs.java +++ b/api/src/main/java/io/minio/CopyObjectArgs.java @@ -69,7 +69,7 @@ public static final class Builder extends ObjectWriteArgs.Builder args.source = source); return this; } diff --git a/api/src/main/java/io/minio/CreateMultipartUploadArgs.java b/api/src/main/java/io/minio/CreateMultipartUploadArgs.java new file mode 100644 index 000000000..32b128e9b --- /dev/null +++ b/api/src/main/java/io/minio/CreateMultipartUploadArgs.java @@ -0,0 +1,65 @@ +/* + * 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.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; +import java.util.Objects; + +/** + * Argument class of {@link MinioAsyncClient#createMultipartUpload} and {@link + * MinioClient#createMultipartUpload}. + */ +public class CreateMultipartUploadArgs extends ObjectArgs { + private Multimap headers = Multimaps.unmodifiableMultimap(HashMultimap.create()); + + public Multimap headers() { + return headers; + } + + public static Builder builder() { + return new Builder(); + } + + /** Argument builder of {@link CreateMultipartUploadArgs}. */ + public static final class Builder extends ObjectArgs.Builder { + @Override + protected void validate(CreateMultipartUploadArgs args) { + super.validate(args); + } + + public Builder headers(Multimap headers) { + operations.add(args -> args.headers = headers); + return this; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof CreateMultipartUploadArgs)) return false; + if (!super.equals(o)) return false; + CreateMultipartUploadArgs that = (CreateMultipartUploadArgs) o; + return Objects.equals(headers, that.headers); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), headers); + } +} diff --git a/api/src/main/java/io/minio/DeleteObjectsArgs.java b/api/src/main/java/io/minio/DeleteObjectsArgs.java new file mode 100644 index 000000000..501fcb6b8 --- /dev/null +++ b/api/src/main/java/io/minio/DeleteObjectsArgs.java @@ -0,0 +1,94 @@ +/* + * 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.DeleteRequest; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; + +/** + * Argument class of {@link MinioAsyncClient#deleteObjects} and {@link MinioClient#deleteObjects}. + */ +public class DeleteObjectsArgs extends BucketArgs { + private boolean quiet; + private boolean bypassGovernanceMode; + private List objects = new LinkedList<>(); + + public boolean quiet() { + return quiet; + } + + public boolean bypassGovernanceMode() { + return bypassGovernanceMode; + } + + public List objects() { + return objects; + } + + public static Builder builder() { + return new Builder(); + } + + /** Argument builder of {@link DeleteObjectsArgs}. */ + public static final class Builder extends BucketArgs.Builder { + @Override + protected void validate(DeleteObjectsArgs args) { + super.validate(args); + Utils.validateNotNull(args.objects, "objects"); + if (args.objects.size() > 1000) { + throw new IllegalArgumentException("list of objects must not be more than 1000"); + } + } + + public Builder quiet(boolean flag) { + operations.add(args -> args.quiet = flag); + return this; + } + + public Builder bypassGovernanceMode(boolean flag) { + operations.add(args -> args.bypassGovernanceMode = flag); + return this; + } + + public Builder objects(List objects) { + Utils.validateNotNull(objects, "objects"); + if (objects.size() > 1000) { + throw new IllegalArgumentException("list of objects must not be more than 1000"); + } + operations.add(args -> args.objects = objects); + return this; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof DeleteObjectsArgs)) return false; + if (!super.equals(o)) return false; + DeleteObjectsArgs that = (DeleteObjectsArgs) o; + return quiet == that.quiet + && bypassGovernanceMode == that.bypassGovernanceMode + && Objects.equals(objects, that.objects); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), quiet, bypassGovernanceMode, objects); + } +} diff --git a/api/src/main/java/io/minio/Digest.java b/api/src/main/java/io/minio/Digest.java deleted file mode 100644 index 22d13134c..000000000 --- a/api/src/main/java/io/minio/Digest.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2015 MinIO, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.minio; - -import com.google.common.io.BaseEncoding; -import io.minio.errors.InsufficientDataException; -import io.minio.errors.InternalException; -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Base64; -import java.util.Locale; - -/** Various global static functions used. */ -public class Digest { - // MD5 hash of zero length byte array. - public static final String ZERO_MD5_HASH = "1B2M2Y8AsgTpgAmY7PhCfg=="; - // SHA-256 hash of zero length byte array. - public static final String ZERO_SHA256_HASH = - "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; - - /** Private constructor. */ - private Digest() {} - - /** Returns MD5 hash of byte array. */ - public static String md5Hash(byte[] data, int length) throws NoSuchAlgorithmException { - MessageDigest md5Digest = MessageDigest.getInstance("MD5"); - md5Digest.update(data, 0, length); - return Base64.getEncoder().encodeToString(md5Digest.digest()); - } - - /** Returns SHA-256 hash of byte array. */ - public static String sha256Hash(byte[] data, int length) throws NoSuchAlgorithmException { - MessageDigest sha256Digest = MessageDigest.getInstance("SHA-256"); - sha256Digest.update((byte[]) data, 0, length); - return BaseEncoding.base16().encode(sha256Digest.digest()).toLowerCase(Locale.US); - } - - /** Returns SHA-256 hash of given string. */ - public static String sha256Hash(String string) throws NoSuchAlgorithmException { - byte[] data = string.getBytes(StandardCharsets.UTF_8); - return sha256Hash(data, data.length); - } - - /** - * Returns SHA-256 and MD5 hashes of given data and it's length. - * - * @param data must be {@link RandomAccessFile}, {@link BufferedInputStream} or byte array. - * @param len length of data to be read for hash calculation. - * @deprecated This method is no longer supported. - */ - @Deprecated - public static String[] sha256Md5Hashes(Object data, int len) - throws NoSuchAlgorithmException, IOException, InsufficientDataException, InternalException { - MessageDigest sha256Digest = MessageDigest.getInstance("SHA-256"); - MessageDigest md5Digest = MessageDigest.getInstance("MD5"); - - if (data instanceof BufferedInputStream || data instanceof RandomAccessFile) { - updateDigests(data, len, sha256Digest, md5Digest); - } else if (data instanceof byte[]) { - sha256Digest.update((byte[]) data, 0, len); - md5Digest.update((byte[]) data, 0, len); - } else { - throw new InternalException( - "Unknown data source to calculate SHA-256 hash. This should not happen, " - + "please report this issue at https://github.com/minio/minio-java/issues", - null); - } - - return new String[] { - BaseEncoding.base16().encode(sha256Digest.digest()).toLowerCase(Locale.US), - BaseEncoding.base64().encode(md5Digest.digest()) - }; - } - - /** Updated MessageDigest with bytes read from file and stream. */ - private static int updateDigests( - Object inputStream, int len, MessageDigest sha256Digest, MessageDigest md5Digest) - throws IOException, InsufficientDataException { - RandomAccessFile file = null; - BufferedInputStream stream = null; - if (inputStream instanceof RandomAccessFile) { - file = (RandomAccessFile) inputStream; - } else if (inputStream instanceof BufferedInputStream) { - stream = (BufferedInputStream) inputStream; - } - - // hold current position of file/stream to reset back to this position. - long pos = 0; - if (file != null) { - pos = file.getFilePointer(); - } else { - stream.mark(len); - } - - // 16KiB buffer for optimization - byte[] buf = new byte[16384]; - int bytesToRead = buf.length; - int bytesRead = 0; - int totalBytesRead = 0; - while (totalBytesRead < len) { - if ((len - totalBytesRead) < bytesToRead) { - bytesToRead = len - totalBytesRead; - } - - if (file != null) { - bytesRead = file.read(buf, 0, bytesToRead); - } else { - bytesRead = stream.read(buf, 0, bytesToRead); - } - - if (bytesRead < 0) { - // reached EOF - throw new InsufficientDataException( - "Insufficient data. bytes read " + totalBytesRead + " expected " + len); - } - - if (bytesRead > 0) { - if (sha256Digest != null) { - sha256Digest.update(buf, 0, bytesRead); - } - - if (md5Digest != null) { - md5Digest.update(buf, 0, bytesRead); - } - - totalBytesRead += bytesRead; - } - } - - // reset back to saved position. - if (file != null) { - file.seek(pos); - } else { - stream.reset(); - } - - return totalBytesRead; - } -} diff --git a/api/src/main/java/io/minio/DownloadObjectArgs.java b/api/src/main/java/io/minio/DownloadObjectArgs.java index eb9fa6daf..e2d066567 100644 --- a/api/src/main/java/io/minio/DownloadObjectArgs.java +++ b/api/src/main/java/io/minio/DownloadObjectArgs.java @@ -40,7 +40,7 @@ public static Builder builder() { /** Argument class of {@link DownloadObjectArgs}. */ public static final class Builder extends ObjectReadArgs.Builder { private void validateFilename(String filename) { - validateNotEmptyString(filename, "filename"); + Utils.validateNotEmptyString(filename, "filename"); } public Builder filename(String filename) { diff --git a/api/src/main/java/io/minio/messages/ParquetInputSerialization.java b/api/src/main/java/io/minio/GetBucketLocationArgs.java similarity index 60% rename from api/src/main/java/io/minio/messages/ParquetInputSerialization.java rename to api/src/main/java/io/minio/GetBucketLocationArgs.java index d40e85960..5d7184e52 100644 --- a/api/src/main/java/io/minio/messages/ParquetInputSerialization.java +++ b/api/src/main/java/io/minio/GetBucketLocationArgs.java @@ -1,5 +1,5 @@ /* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2019 MinIO, Inc. + * 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. @@ -14,13 +14,17 @@ * limitations under the License. */ -package io.minio.messages; - -import org.simpleframework.xml.Root; +package io.minio; /** - * Helper class to denote Parquet input serialization request XML as per {@link - * SelectObjectContentRequest}. + * Argument class of {@link MinioAsyncClient#getBucketLocation} and {@link + * MinioClient#getBucketLocation}. */ -@Root(name = "Parquet") -public class ParquetInputSerialization {} +public class GetBucketLocationArgs extends BucketArgs { + public static Builder builder() { + return new Builder(); + } + + /** Argument builder of {@link GetBucketLocationArgs}. */ + public static final class Builder extends BucketArgs.Builder {} +} diff --git a/api/src/main/java/io/minio/GetObjectAttributesArgs.java b/api/src/main/java/io/minio/GetObjectAttributesArgs.java index 19bd9bbba..143275af1 100644 --- a/api/src/main/java/io/minio/GetObjectAttributesArgs.java +++ b/api/src/main/java/io/minio/GetObjectAttributesArgs.java @@ -51,7 +51,7 @@ public static final class Builder @Override protected void validate(GetObjectAttributesArgs args) { super.validate(args); - validateNotNull(args.objectAttributes, "object attributes"); + Utils.validateNotNull(args.objectAttributes, "object attributes"); } public Builder objectAttributes(String[] objectAttributes) { diff --git a/api/src/main/java/io/minio/GetPresignedObjectUrlArgs.java b/api/src/main/java/io/minio/GetPresignedObjectUrlArgs.java index a6e3d2a23..ed418ecdd 100644 --- a/api/src/main/java/io/minio/GetPresignedObjectUrlArgs.java +++ b/api/src/main/java/io/minio/GetPresignedObjectUrlArgs.java @@ -16,7 +16,6 @@ package io.minio; -import io.minio.http.Method; import java.util.Objects; import java.util.concurrent.TimeUnit; @@ -28,10 +27,10 @@ public class GetPresignedObjectUrlArgs extends ObjectVersionArgs { // default expiration for a presigned URL is 7 days in seconds public static final int DEFAULT_EXPIRY_TIME = (int) TimeUnit.DAYS.toSeconds(7); - private Method method; + private Http.Method method; private int expiry = DEFAULT_EXPIRY_TIME; - public Method method() { + public Http.Method method() { return method; } @@ -46,8 +45,8 @@ public static Builder builder() { /** Argument builder of {@link GetPresignedObjectUrlArgs}. */ public static final class Builder extends ObjectVersionArgs.Builder { - private void validateMethod(Method method) { - validateNotNull(method, "method"); + private void validateMethod(Http.Method method) { + Utils.validateNotNull(method, "method"); } private void validateExpiry(int expiry) { @@ -60,7 +59,7 @@ private void validateExpiry(int expiry) { } /* method HTTP {@link Method} to generate presigned URL. */ - public Builder method(Method method) { + public Builder method(Http.Method method) { validateMethod(method); operations.add(args -> args.method = method); return this; diff --git a/api/src/main/java/io/minio/HeadObjectArgs.java b/api/src/main/java/io/minio/HeadObjectArgs.java new file mode 100644 index 000000000..c13d01e97 --- /dev/null +++ b/api/src/main/java/io/minio/HeadObjectArgs.java @@ -0,0 +1,40 @@ +/* + * 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#headObject} and {@link MinioClient#headObject}. */ +public class HeadObjectArgs extends ObjectConditionalReadArgs { + protected HeadObjectArgs() {} + + public HeadObjectArgs(ObjectReadArgs args) { + this.extraHeaders = args.extraHeaders; + this.extraQueryParams = args.extraQueryParams; + this.bucketName = args.bucketName; + this.region = args.region; + this.objectName = args.objectName; + this.versionId = args.versionId; + this.ssec = args.ssec; + } + + public static Builder builder() { + return new Builder(); + } + + /** Argument builder of {@link HeadObjectArgs}. */ + public static final class Builder + extends ObjectConditionalReadArgs.Builder {} +} diff --git a/api/src/main/java/io/minio/HeadObjectResponse.java b/api/src/main/java/io/minio/HeadObjectResponse.java new file mode 100644 index 000000000..a8a3c1c1c --- /dev/null +++ b/api/src/main/java/io/minio/HeadObjectResponse.java @@ -0,0 +1,127 @@ +/* + * 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.LegalHold; +import io.minio.messages.RetentionMode; +import java.time.ZonedDateTime; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import okhttp3.Headers; + +/** Response of {@link S3Base#headObjectAsync}. */ +public class HeadObjectResponse extends GenericResponse { + private String etag; + private long size; + private ZonedDateTime lastModified; + private RetentionMode retentionMode; + private ZonedDateTime retentionRetainUntilDate; + private LegalHold legalHold; + private boolean deleteMarker; + private Map userMetadata; + + public HeadObjectResponse(Headers headers, String bucket, String region, String object) { + super(headers, bucket, region, object); + String value; + + value = headers.get("ETag"); + this.etag = (value != null ? value.replaceAll("\"", "") : ""); + + value = headers.get("Content-Length"); + this.size = (value != null ? Long.parseLong(value) : -1); + + this.lastModified = + ZonedDateTime.parse(headers.get("Last-Modified"), Time.HTTP_HEADER_DATE_FORMAT); + + value = headers.get("x-amz-object-lock-mode"); + this.retentionMode = (value != null ? RetentionMode.valueOf(value) : null); + + value = headers.get("x-amz-object-lock-retain-until-date"); + this.retentionRetainUntilDate = + value == null ? null : Time.S3Time.fromString(value).toZonedDateTime(); + + this.legalHold = new LegalHold("ON".equals(headers.get("x-amz-object-lock-legal-hold"))); + + this.deleteMarker = Boolean.parseBoolean(headers.get("x-amz-delete-marker")); + + Map userMetadata = new HashMap<>(); + for (String key : headers.names()) { + if (key.toLowerCase(Locale.US).startsWith("x-amz-meta-")) { + userMetadata.put( + key.toLowerCase(Locale.US).substring("x-amz-meta-".length(), key.length()), + headers.get(key)); + } + } + + this.userMetadata = Utils.unmodifiableMap(userMetadata); + } + + public String etag() { + return etag; + } + + public long size() { + return size; + } + + public ZonedDateTime lastModified() { + return lastModified; + } + + public RetentionMode retentionMode() { + return retentionMode; + } + + public ZonedDateTime retentionRetainUntilDate() { + return retentionRetainUntilDate; + } + + public LegalHold legalHold() { + return legalHold; + } + + public boolean deleteMarker() { + return deleteMarker; + } + + public String versionId() { + return this.headers().get("x-amz-version-id"); + } + + public String contentType() { + return this.headers().get("Content-Type"); + } + + public Map userMetadata() { + return userMetadata; + } + + @Override + public String toString() { + return "ObjectHead{" + + "bucket=" + + bucket() + + ", object=" + + object() + + ", last-modified=" + + lastModified + + ", size=" + + size + + "}"; + } +} diff --git a/api/src/main/java/io/minio/Http.java b/api/src/main/java/io/minio/Http.java new file mode 100644 index 000000000..848ab4a0d --- /dev/null +++ b/api/src/main/java/io/minio/Http.java @@ -0,0 +1,943 @@ +/* + * 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.google.common.collect.Multimap; +import io.minio.credentials.Credentials; +import io.minio.errors.XmlParserException; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.channels.Channels; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.NoSuchAlgorithmException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.time.ZonedDateTime; +import java.util.Arrays; +import java.util.Collection; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nonnull; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; +import okhttp3.Headers; +import okhttp3.HttpUrl; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Protocol; +import okio.BufferedSink; +import okio.Okio; +import java.net.URL; + +/** HTTP utilities. */ +public class Http { + public static class BaseUrl { + private HttpUrl url; + private String awsS3Prefix; + private String awsDomainSuffix; + private boolean awsDualstack; + private String region; + private boolean useVirtualStyle; + + public BaseUrl(String endpoint) { + setUrl(parse(endpoint)); + } + + public BaseUrl(String endpoint, int port, boolean secure) { + HttpUrl url = parse(endpoint); + if (port < 1 || port > 65535) { + throw new IllegalArgumentException("port must be in range of 1 to 65535"); + } + url = url.newBuilder().port(port).scheme(secure ? "https" : "http").build(); + + setUrl(url); + } + + public BaseUrl(HttpUrl url) { + Utils.validateNotNull(url, "url"); + Utils.validateUrl(url); + setUrl(url); + } + + public BaseUrl(URL url) { + Utils.validateNotNull(url, "url"); + return this(HttpUrl.get(url)); + } + + private void setAwsInfo(String host, boolean https) { + this.awsS3Prefix = null; + this.awsDomainSuffix = null; + this.awsDualstack = false; + + if (!Utils.HOSTNAME_REGEX.matcher(host).find()) return; + + if (Utils.AWS_ELB_ENDPOINT_REGEX.matcher(host).find()) { + String[] tokens = host.split("\\.elb\\.amazonaws\\.com", 1)[0].split("\\."); + this.region = tokens[tokens.length - 1]; + return; + } + + if (!Utils.AWS_ENDPOINT_REGEX.matcher(host).find()) return; + + if (!Utils.AWS_S3_ENDPOINT_REGEX.matcher(host).find()) { + throw new IllegalArgumentException("invalid Amazon AWS host " + host); + } + + Matcher matcher = Utils.AWS_S3_PREFIX_REGEX.matcher(host); + matcher.lookingAt(); + int end = matcher.end(); + + this.awsS3Prefix = host.substring(0, end); + if (this.awsS3Prefix.contains("s3-accesspoint") && !https) { + throw new IllegalArgumentException("use HTTPS scheme for host " + host); + } + + String[] tokens = host.substring(end).split("\\."); + awsDualstack = "dualstack".equals(tokens[0]); + if (awsDualstack) tokens = Arrays.copyOfRange(tokens, 1, tokens.length); + String regionInHost = null; + if (!tokens[0].equals("vpce") && !tokens[0].equals("amazonaws")) { + regionInHost = tokens[0]; + tokens = Arrays.copyOfRange(tokens, 1, tokens.length); + } + this.awsDomainSuffix = String.join(".", tokens); + + if (host.equals("s3-external-1.amazonaws.com")) regionInHost = "us-east-1"; + if (host.equals("s3-us-gov-west-1.amazonaws.com") + || host.equals("s3-fips-us-gov-west-1.amazonaws.com")) { + regionInHost = "us-gov-west-1"; + } + + if (regionInHost != null) this.region = regionInHost; + } + + private void setUrl(HttpUrl url) { + this.url = url; + this.setAwsInfo(url.host(), url.isHttps()); + this.useVirtualStyle = this.awsDomainSuffix != null || url.host().endsWith("aliyuncs.com"); + } + + private HttpUrl parse(String endpoint) { + Utils.validateNotEmptyString(endpoint, "endpoint"); + HttpUrl url = HttpUrl.parse(endpoint); + if (url == null) { + Utils.validateHostnameOrIPAddress(endpoint); + url = new HttpUrl.Builder().scheme("https").host(endpoint).build(); + } else { + Utils.validateUrl(url); + } + return url; + } + + public String awsS3Prefix() { + return awsS3Prefix; + } + + public String awsDomainSuffix() { + return awsDomainSuffix; + } + + public String region() { + return region; + } + + public void setRegion(String region) { + this.region = region; + } + + /** Enables dual-stack endpoint for Amazon S3 endpoint. */ + public void enableDualStackEndpoint() { + awsDualstack = true; + } + + /** Disables dual-stack endpoint for Amazon S3 endpoint. */ + public void disableDualStackEndpoint() { + awsDualstack = false; + } + + /** Enables virtual-style endpoint. */ + public void enableVirtualStyleEndpoint() { + useVirtualStyle = true; + } + + /** Disables virtual-style endpoint. */ + public void disableVirtualStyleEndpoint() { + useVirtualStyle = false; + } + + /** Sets AWS S3 domain prefix. */ + public void setAwsS3Prefix(@Nonnull String awsS3Prefix) { + if (awsS3Prefix == null) + throw new IllegalArgumentException("null Amazon AWS S3 domain prefix"); + if (!Utils.AWS_S3_PREFIX_REGEX.matcher(awsS3Prefix).find()) { + throw new IllegalArgumentException("invalid Amazon AWS S3 domain prefix " + awsS3Prefix); + } + this.awsS3Prefix = awsS3Prefix; + } + + private String buildAwsUrl( + HttpUrl.Builder builder, String bucketName, boolean enforcePathStyle, String region) { + String host = this.awsS3Prefix + this.awsDomainSuffix; + if (host.equals("s3-external-1.amazonaws.com") + || host.equals("s3-us-gov-west-1.amazonaws.com") + || host.equals("s3-fips-us-gov-west-1.amazonaws.com")) { + builder.host(host); + return host; + } + + host = this.awsS3Prefix; + if (this.awsS3Prefix.contains("s3-accelerate")) { + if (bucketName.contains(".")) { + throw new IllegalArgumentException( + "bucket name '" + bucketName + "' with '.' is not allowed for accelerate endpoint"); + } + if (enforcePathStyle) host = host.replaceFirst("-accelerate", ""); + } + + if (this.awsDualstack) host += "dualstack."; + if (!this.awsS3Prefix.contains("s3-accelerate")) host += region + "."; + host += this.awsDomainSuffix; + + builder.host(host); + return host; + } + + private String buildListBucketsUrl(HttpUrl.Builder builder, String region) { + if (this.awsDomainSuffix == null) return null; + + String host = this.awsS3Prefix + this.awsDomainSuffix; + if (host.equals("s3-external-1.amazonaws.com") + || host.equals("s3-us-gov-west-1.amazonaws.com") + || host.equals("s3-fips-us-gov-west-1.amazonaws.com")) { + builder.host(host); + return host; + } + + String s3Prefix = this.awsS3Prefix; + String domainSuffix = this.awsDomainSuffix; + if (this.awsS3Prefix.startsWith("s3.") || this.awsS3Prefix.startsWith("s3-")) { + s3Prefix = "s3."; + domainSuffix = "amazonaws.com" + (domainSuffix.endsWith(".cn") ? ".cn" : ""); + } + + host = s3Prefix + region + "." + domainSuffix; + builder.host(host); + return host; + } + + /** Build URL for given parameters. */ + public HttpUrl buildUrl( + Method method, + String bucketName, + String objectName, + String region, + Multimap queryParams) + throws NoSuchAlgorithmException { + if (bucketName == null && objectName != null) { + throw new IllegalArgumentException("null bucket name for object '" + objectName + "'"); + } + + HttpUrl.Builder urlBuilder = this.baseUrl.newBuilder(); + + if (queryParams != null) { + for (Map.Entry entry : queryParams.entries()) { + urlBuilder.addEncodedQueryParameter( + Utils.encode(entry.getKey()), Utils.encode(entry.getValue())); + } + } + + if (bucketName == null) { + this.buildListBucketsUrl(urlBuilder, region); + return urlBuilder.build(); + } + + boolean enforcePathStyle = ( + // use path style for make bucket to workaround "AuthorizationHeaderMalformed" error from + // s3.amazonaws.com + (method == Method.PUT && objectName == null && queryParams == null) + + // use path style for location query + || (queryParams != null && queryParams.containsKey("location")) + + // use path style where '.' in bucketName causes SSL certificate validation error + || (bucketName.contains(".") && this.baseUrl.isHttps())); + + String host = this.baseUrl.host(); + if (this.awsDomainSuffix != null) { + host = this.buildAwsUrl(urlBuilder, bucketName, enforcePathStyle, region); + } + + if (enforcePathStyle || !this.useVirtualStyle) { + urlBuilder.addEncodedPathSegment(Utils.encode(bucketName)); + } else { + urlBuilder.host(bucketName + "." + host); + } + + if (objectName != null) { + urlBuilder.addEncodedPathSegments(Utils.encodePath(objectName)); + } + + return urlBuilder.build(); + } + + @Override + public String toString() { + return url.toString(); + } + } + + public static final MediaType DEFAULT_MEDIA_TYPE = MediaType.parse("application/octet-stream"); + public static final MediaType XML_MEDIA_TYPE = MediaType.parse("application/xml"); + public static final String US_EAST_1 = "us-east-1"; + public static final long DEFAULT_TIMEOUT = TimeUnit.MINUTES.toMillis(5); + + public static MediaType mediaType(String value) { + if (value == null) return DEFAULT_MEDIA_TYPE; + MediaType mediaType = MediaType.parse(value); + if (mediaType == null) { + throw new IllegalArgumentException( + "invalid media/content type '" + value + "' as per RFC 2045"); + } + return mediaType; + } + + private static OkHttpClient enableJKSPKCS12Certificates( + OkHttpClient httpClient, + String trustStorePath, + String trustStorePassword, + String keyStorePath, + String keyStorePassword, + String keyStoreType) + throws GeneralSecurityException, IOException { + if (trustStorePath == null || trustStorePath.isEmpty()) { + throw new IllegalArgumentException("trust store path must be provided"); + } + if (trustStorePassword == null) { + throw new IllegalArgumentException("trust store password must be provided"); + } + if (keyStorePath == null || keyStorePath.isEmpty()) { + throw new IllegalArgumentException("key store path must be provided"); + } + if (keyStorePassword == null) { + throw new IllegalArgumentException("key store password must be provided"); + } + + SSLContext sslContext = SSLContext.getInstance("TLS"); + KeyStore trustStore = KeyStore.getInstance("JKS"); + KeyStore keyStore = KeyStore.getInstance(keyStoreType); + try (FileInputStream trustInput = new FileInputStream(trustStorePath); + FileInputStream keyInput = new FileInputStream(keyStorePath); ) { + trustStore.load(trustInput, trustStorePassword.toCharArray()); + keyStore.load(keyInput, keyStorePassword.toCharArray()); + } + TrustManagerFactory trustManagerFactory = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(trustStore); + + KeyManagerFactory keyManagerFactory = + KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(keyStore, keyStorePassword.toCharArray()); + + sslContext.init( + keyManagerFactory.getKeyManagers(), + trustManagerFactory.getTrustManagers(), + new java.security.SecureRandom()); + + return httpClient + .newBuilder() + .sslSocketFactory( + sslContext.getSocketFactory(), + (X509TrustManager) trustManagerFactory.getTrustManagers()[0]) + .build(); + } + + public static OkHttpClient enableJKSCertificates( + OkHttpClient httpClient, + String trustStorePath, + String trustStorePassword, + String keyStorePath, + String keyStorePassword) + throws GeneralSecurityException, IOException { + return enableJKSPKCS12Certificates( + httpClient, trustStorePath, trustStorePassword, keyStorePath, keyStorePassword, "JKS"); + } + + public static OkHttpClient enablePKCS12Certificates( + OkHttpClient httpClient, + String trustStorePath, + String trustStorePassword, + String keyStorePath, + String keyStorePassword) + throws GeneralSecurityException, IOException { + return enableJKSPKCS12Certificates( + httpClient, trustStorePath, trustStorePassword, keyStorePath, keyStorePassword, "PKCS12"); + } + + /** + * copied logic from + * https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/CustomTrust.java + */ + public static OkHttpClient enableExternalCertificates(OkHttpClient httpClient, String filename) + throws GeneralSecurityException, IOException { + Collection certificates = null; + try (FileInputStream fis = new FileInputStream(filename)) { + certificates = CertificateFactory.getInstance("X.509").generateCertificates(fis); + } + + if (certificates == null || certificates.isEmpty()) { + throw new IllegalArgumentException("expected non-empty set of trusted certificates"); + } + + char[] password = "password".toCharArray(); // Any password will work. + + // Put the certificates a key store. + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + // By convention, 'null' creates an empty key store. + keyStore.load(null, password); + + int index = 0; + for (Certificate certificate : certificates) { + String certificateAlias = Integer.toString(index++); + keyStore.setCertificateEntry(certificateAlias, certificate); + } + + // Use it to build an X509 trust manager. + KeyManagerFactory keyManagerFactory = + KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(keyStore, password); + TrustManagerFactory trustManagerFactory = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(keyStore); + + final KeyManager[] keyManagers = keyManagerFactory.getKeyManagers(); + final TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); + + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(keyManagers, trustManagers, null); + SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); + + return httpClient + .newBuilder() + .sslSocketFactory(sslSocketFactory, (X509TrustManager) trustManagers[0]) + .build(); + } + + public static OkHttpClient newDefaultClient() { + OkHttpClient httpClient = + new OkHttpClient() + .newBuilder() + .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS) + .writeTimeout(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS) + .readTimeout(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS) + .protocols(Arrays.asList(Protocol.HTTP_1_1)) + .build(); + String filename = System.getenv("SSL_CERT_FILE"); + if (filename != null && !filename.isEmpty()) { + try { + httpClient = enableExternalCertificates(httpClient, filename); + } catch (GeneralSecurityException | IOException e) { + throw new RuntimeException(e); + } + } + return httpClient; + } + + @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( + value = "SIC", + justification = "Should not be used in production anyways.") + public static OkHttpClient disableCertCheck(OkHttpClient client) + throws KeyManagementException, NoSuchAlgorithmException { + final TrustManager[] trustAllCerts = + new TrustManager[] { + new X509TrustManager() { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) + throws CertificateException {} + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) + throws CertificateException {} + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[] {}; + } + } + }; + + final SSLContext sslContext = SSLContext.getInstance("SSL"); + sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); + final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); + + return client + .newBuilder() + .sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]) + .hostnameVerifier( + new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + }) + .build(); + } + + public static OkHttpClient setTimeout( + OkHttpClient client, long connectTimeout, long writeTimeout, long readTimeout) { + return client + .newBuilder() + .connectTimeout(connectTimeout, TimeUnit.MILLISECONDS) + .writeTimeout(writeTimeout, TimeUnit.MILLISECONDS) + .readTimeout(readTimeout, TimeUnit.MILLISECONDS) + .build(); + } + + public static okhttp3.Request newRequest( + okhttp3.HttpUrl url, + Method method, + okhttp3.Headers headers, + RequestBody body, + String userAgent) { + okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder(); + requestBuilder.url(url); + + if (headers != null) requestBuilder.headers(headers); + requestBuilder.header("Accept-Encoding", "identity"); // Disable default okhttp gzip compression + requestBuilder.header("User-Agent", userAgent); + requestBuilder.header("Host", Utils.getHostHeader(url)); + + if (body != null) { + requestBuilder.header("Content-Type", body.contentType().toString()); + requestBuilder.header("Content-Length", String.valueOf(body.contentLength())); + } + + return requestBuilder.method(method.toString(), body).build(); + } + + public static class S3Request { + private String userAgent; + private Method method; + private String bucket; + private String region; + private String object; + private Multimap headers; + private Multimap queryParams; + private MediaType contentType; + + private okhttp3.RequestBody requestBody; + private RandomAccessFile file; + private ByteBuffer buffer; + private byte[] data; + private Long length; + private String sha256Hash; + private String md5Hash; + private boolean traceBody; + private boolean retryFailure; + + private String traces = null; + + private S3Request(Builder builder) { + this.userAgent = builder.userAgent; + this.method = builder.method; + this.bucket = builder.bucket; + this.region = builder.region; + this.object = builder.object; + this.headers = builder.headers; + this.queryParams = builder.queryParams; + this.contentType = builder.contentType; + this.requestBody = builder.requestBody; + this.file = builder.file; + this.buffer = builder.buffer; + this.data = builder.data; + this.length = builder.length; + this.sha256Hash = builder.sha256Hash; + this.md5Hash = builder.md5Hash; + this.traceBody = builder.traceBody; + this.retryFailure = builder.retryFailure; + } + + public String userAgent() { + return userAgent; + } + + public Method method() { + return method; + } + + public String bucket() { + return bucket; + } + + public String region() { + return region; + } + + public String object() { + return object; + } + + public Multimap headers() { + return headers; + } + + public Multimap queryParams() { + return queryParams; + } + + public MediaType contentType() { + return contentType; + } + + public okhttp3.RequestBody requestBody() { + return requestBody; + } + + public RandomAccessFile file() { + return file; + } + + public ByteBuffer buffer() { + return buffer; + } + + public byte[] data() { + return data; + } + + public Long length() { + return length; + } + + public String sha256Hash() { + return sha256Hash; + } + + public String md5Hash() { + return md5Hash; + } + + public boolean traceBody() { + return traceBody; + } + + public boolean retryFailure() { + return retryFailure; + } + + public String traces() { + return traces; + } + + public okhttp3.Request httpRequest(HttpUrl url, Credentials credentials) + throws InvalidKeyException, IOException, NoSuchAlgorithmException { + Headers headers = Utils.httpHeaders(this.headers); + + if (requestBody != null) { + return newRequest(url, method, headers, new RequestBody(requestBody), userAgent); + } + + if (credentials == null) { + if (md5Hash == null) throw new IllegalArgumentException("MD5 hash must be provided"); + } else if (!url.isHttps()) { + if (sha256Hash == null) throw new IllegalArgumentException("SHA256 hash must be provided"); + } else if (sha256Hash == null) { + sha256Hash = "UNSIGNED-PAYLOAD"; + } + + { + Headers.Builder builder = new Headers.Builder(); + builder.addAll(headers); + builder.add("Content-Type", contentType.toString()); + if (md5Hash != null) builder.add("Content-MD5", md5Hash); + if (sha256Hash != null) builder.add("x-amz-content-sha256", sha256Hash); + headers = builder.build(); + } + + RequestBody requestBody = null; + if (file != null) { + requestBody = new RequestBody(file, length, contentType); + } else if (buffer != null) { + requestBody = new RequestBody(buffer, contentType); + } else { + requestBody = new RequestBody(data, length.intValue(), contentType); + } + + okhttp3.Request request = newRequest(url, method, headers, requestBody, userAgent); + if (credentials != null) { + // Sign the request + okhttp3.Request.Builder builder = request.newBuilder(); + String sessionToken = credentials.sessionToken(); + if (sessionToken != null) builder.header("X-Amz-Security-Token", sessionToken); + builder.header("x-amz-date", ZonedDateTime.now().format(Time.AMZ_DATE_FORMAT)); + request = builder.build(); + request = + Signer.signV4S3( + request, region, credentials.accessKey(), credentials.secretKey(), sha256Hash); + } + + StringBuilder traceBuilder = new StringBuilder(); + traceBuilder.append("---------START-HTTP---------\n"); + String encodedPath = request.url().encodedPath(); + String encodedQuery = request.url().encodedQuery(); + if (encodedQuery != null) encodedPath += "?" + encodedQuery; + traceBuilder.append(request.method()).append(" ").append(encodedPath).append(" HTTP/1.1\n"); + traceBuilder + .append( + request + .headers() + .toString() + .replaceAll("Signature=([0-9a-f]+)", "Signature=*REDACTED*") + .replaceAll("Credential=([^/]+)", "Credential=*REDACTED*")) + .append("\n\n"); + if (data != null && traceBody) { + String value = new String(data, StandardCharsets.UTF_8); + traceBuilder.append(value); + if (!value.endsWith("\n")) traceBuilder.append("\n"); + } + traces = traceBuilder.toString(); + + return request; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String userAgent; + private Method method; + private String bucket = null; + private String region = US_EAST_1; + private String object = null; + private Multimap headers = null; + private Multimap queryParams = null; + private MediaType contentType = DEFAULT_MEDIA_TYPE; + + private okhttp3.RequestBody requestBody = null; + private RandomAccessFile file = null; + private ByteBuffer buffer = null; + private byte[] data = null; + private Long length = null; + private String sha256Hash = null; + private String md5Hash = null; + private boolean traceBody = false; + private boolean retryFailure = false; + + public Builder userAgent(String userAgent) { + this.userAgent = Utils.validateNotNull(userAgent, "user agent"); + return this; + } + + public Builder method(Method method) { + this.method = Utils.validateNotNull(method, "HTTP method"); + return this; + } + + public Builder bucket(String bucket) { + this.bucket = bucket; + return this; + } + + public Builder region(String region) { + this.region = region; + return this; + } + + public Builder object(String object) { + this.object = object; + return this; + } + + public Builder headers(Multimap headers) { + this.headers = headers; + return this; + } + + public Builder queryParams(Multimap queryParams) { + this.queryParams = queryParams; + return this; + } + + public Builder baseArgs(BaseArgs baseArgs) { + if (baseArgs != null) { + headers = Utils.mergeMultimap(baseArgs.extraHeaders(), headers); + queryParams = Utils.mergeMultimap(baseArgs.extraQueryParams(), queryParams); + if (baseArgs instanceof BucketArgs) { + bucket = ((BucketArgs) baseArgs).bucket(); + region = ((BucketArgs) baseArgs).region(); + } + if (baseArgs instanceof ObjectArgs) object = ((ObjectArgs) baseArgs).object(); + } + return this; + } + + public Builder body( + Object body, Long length, MediaType contentType, String sha256Hash, String md5Hash) + throws XmlParserException { + Utils.validateNotNull(body, "body"); + if (length != null && length < 0) { + throw new IllegalArgumentException("valid length must be provided"); + } + + this.contentType = contentType; + this.sha256Hash = sha256Hash; + this.md5Hash = md5Hash; + + if (body instanceof okhttp3.RequestBody) { + this.requestBody = (okhttp3.RequestBody) body; + } else if (body instanceof ByteBuffer) { + this.buffer = (ByteBuffer) body; + } else if (body instanceof RandomAccessFile) { + if (length == null) { + throw new IllegalArgumentException( + "valid length must be provided for random access file"); + } + this.file = (RandomAccessFile) body; + this.length = length; + } else if (body instanceof byte[]) { + if (length == null) { + throw new IllegalArgumentException("valid length must be provided for byte array body"); + } + this.data = (byte[]) body; + this.length = length; + } else { + this.traceBody = true; + if (body instanceof CharSequence) { + this.data = ((CharSequence) body).toString().getBytes(StandardCharsets.UTF_8); + this.length = (long) this.data.length; + } else { + // For any other object, do XML marshalling. + this.data = Xml.marshal(body).getBytes(StandardCharsets.UTF_8); + this.length = length; + if (contentType == null) this.contentType = XML_MEDIA_TYPE; + } + } + + return this; + } + + public S3Request build() { + if (method == null) throw new IllegalArgumentException("method must be provided"); + if (userAgent == null) throw new IllegalArgumentException("user agent must be provided"); + + if ((method == Method.PUT || method == Method.POST) + && requestBody == null + && file == null + && buffer == null + && data == null) { + data = Utils.EMPTY_BODY; + length = 0L; + sha256Hash = Checksum.ZERO_SHA256_HASH; + md5Hash = Checksum.ZERO_MD5_HASH; + } + + return new S3Request(this); + } + } + } + + /** RequestBody that wraps a single data object. */ + public static class RequestBody extends okhttp3.RequestBody { + private okhttp3.RequestBody body; + private ByteBuffer buffer; + private RandomAccessFile file; + private long position; + private byte[] bytes; + private long length; + private MediaType contentType; + + public RequestBody( + @Nonnull final byte[] bytes, final int length, @Nonnull final MediaType contentType) { + this.bytes = Utils.validateNotNull(bytes, "data bytes"); + if (length < 0) throw new IllegalArgumentException("length must not be negative value"); + this.length = length; + this.contentType = Utils.validateNotNull(contentType, "content type"); + } + + public RequestBody( + @Nonnull final RandomAccessFile file, + final long length, + @Nonnull final MediaType contentType) { + this.file = Utils.validateNotNull(file, "randome access file"); + if (length < 0) throw new IllegalArgumentException("length must not be negative value"); + this.length = length; + this.contentType = Utils.validateNotNull(contentType, "content type"); + this.position = file.getFilePointer(); + } + + public RequestBody(@Nonnull final ByteBuffer buffer, @Nonnull final MediaType contentType) { + this.buffer = Utils.validateNotNull(buffer, "buffer"); + this.length = buffer.length(); + this.contentType = Utils.validateNotNull(body.contentType(), "content type"); + } + + public RequestBody(@Nonnull final okhttp3.RequestBody body) throws IOException { + this.body = Utils.validateNotNull(body, "body"); + if (body.contentLength() < 0) { + throw new IllegalArgumentException("length must not be negative value"); + } + this.length = body.contentLength(); + this.contentType = Utils.validateNotNull(body.contentType(), "content type"); + } + + @Override + public MediaType contentType() { + return contentType; + } + + @Override + public long contentLength() { + return length; + } + + @Override + public void writeTo(BufferedSink sink) throws IOException { + if (body != null) { + body.writeTo(sink); + } else if (buffer != null) { + sink.write(Okio.source(buffer.inputStream()), length); + } else if (file != null) { + file.seek(position); + sink.write(Okio.source(Channels.newInputStream(file.getChannel())), length); + } else { + sink.write(bytes, 0, (int) length); + } + } + } + + /** HTTP methods. */ + public static enum Method { + GET, + HEAD, + POST, + PUT, + DELETE; + } +} diff --git a/api/src/main/java/io/minio/HttpRequestBody.java b/api/src/main/java/io/minio/HttpRequestBody.java deleted file mode 100644 index cbc59706c..000000000 --- a/api/src/main/java/io/minio/HttpRequestBody.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2015 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.IOException; -import okhttp3.MediaType; -import okhttp3.RequestBody; -import okio.BufferedSink; - -/** RequestBody that wraps a single data object. */ -class HttpRequestBody extends RequestBody { - private PartSource partSource; - private byte[] bytes; - private int length; - private String contentType; - - HttpRequestBody(final PartSource partSource, final String contentType) { - this.partSource = partSource; - this.contentType = contentType; - } - - HttpRequestBody(final byte[] bytes, final int length, final String contentType) { - this.bytes = bytes; - this.length = length; - this.contentType = contentType; - } - - @Override - public MediaType contentType() { - MediaType mediaType = null; - if (contentType != null) mediaType = MediaType.parse(contentType); - return (mediaType == null) ? MediaType.parse("application/octet-stream") : mediaType; - } - - @Override - public long contentLength() { - return (partSource != null) ? partSource.size() : length; - } - - @Override - public void writeTo(BufferedSink sink) throws IOException { - if (partSource != null) { - sink.write(partSource.source(), partSource.size()); - } else { - sink.write(bytes, 0, length); - } - } -} diff --git a/api/src/main/java/io/minio/ListBucketsArgs.java b/api/src/main/java/io/minio/ListBucketsArgs.java index 539e4192f..0651dcef2 100644 --- a/api/src/main/java/io/minio/ListBucketsArgs.java +++ b/api/src/main/java/io/minio/ListBucketsArgs.java @@ -51,7 +51,7 @@ public static final class Builder extends BaseArgs.Builder args.bucketRegion = region); return this; } @@ -66,13 +66,13 @@ public Builder maxBuckets(int maxBuckets) { } public Builder prefix(String prefix) { - validateNullOrNotEmptyString(prefix, "prefix"); + Utils.validateNullOrNotEmptyString(prefix, "prefix"); operations.add(args -> args.prefix = prefix); return this; } public Builder continuationToken(String continuationToken) { - validateNullOrNotEmptyString(continuationToken, "continuation token"); + Utils.validateNullOrNotEmptyString(continuationToken, "continuation token"); operations.add(args -> args.continuationToken = continuationToken); return this; } diff --git a/api/src/main/java/io/minio/ListMultipartUploadsArgs.java b/api/src/main/java/io/minio/ListMultipartUploadsArgs.java new file mode 100644 index 000000000..33561782b --- /dev/null +++ b/api/src/main/java/io/minio/ListMultipartUploadsArgs.java @@ -0,0 +1,117 @@ +/* + * 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.Objects; + +/** + * Argument class of {@link MinioAsyncClient#listMultipartUploads} and {@link + * MinioClient#listMultipartUploads}. + */ +public class ListMultipartUploadsArgs extends BucketArgs { + private String delimiter; + private String encodingType; + private Integer maxUploads; + private String prefix; + private String keyMarker; + private String uploadIdMarker; + + public String delimiter() { + return delimiter; + } + + public String encodingType() { + return encodingType; + } + + public Integer maxUploads() { + return maxUploads; + } + + public String prefix() { + return prefix; + } + + public String keyMarker() { + return keyMarker; + } + + public String uploadIdMarker() { + return uploadIdMarker; + } + + public static Builder builder() { + return new Builder(); + } + + /** Argument builder of {@link ListMultipartUploadsArgs}. */ + public static final class Builder extends BucketArgs.Builder { + public Builder delimiter(String delimiter) { + operations.add(args -> args.delimiter = delimiter); + return this; + } + + public Builder encodingType(String encodingType) { + operations.add(args -> args.encodingType = encodingType); + return this; + } + + public Builder maxUploads(Integer maxUploads) { + if (maxUploads != null && maxUploads < 1) { + throw new IllegalArgumentException("valid max keys must be provided"); + } + + operations.add(args -> args.maxUploads = maxUploads); + return this; + } + + public Builder prefix(String prefix) { + operations.add(args -> args.prefix = prefix); + return this; + } + + public Builder keyMarker(String keyMarker) { + operations.add(args -> args.keyMarker = keyMarker); + return this; + } + + public Builder uploadIdMarker(String uploadIdMarker) { + operations.add(args -> args.uploadIdMarker = uploadIdMarker); + return this; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ListMultipartUploadsArgs)) return false; + if (!super.equals(o)) return false; + ListMultipartUploadsArgs that = (ListMultipartUploadsArgs) o; + return Objects.equals(delimiter, that.delimiter) + && Objects.equals(encodingType, that.encodingType) + && Objects.equals(maxUploads, that.maxUploads) + && Objects.equals(prefix, that.prefix) + && Objects.equals(keyMarker, that.keyMarker) + && Objects.equals(uploadIdMarker, that.uploadIdMarker); + } + + @Override + public int hashCode() { + return Objects.hash( + super.hashCode(), delimiter, encodingType, maxUploads, prefix, keyMarker, uploadIdMarker); + } +} diff --git a/api/src/main/java/io/minio/ListObjectVersionsArgs.java b/api/src/main/java/io/minio/ListObjectVersionsArgs.java new file mode 100644 index 000000000..f087dece6 --- /dev/null +++ b/api/src/main/java/io/minio/ListObjectVersionsArgs.java @@ -0,0 +1,117 @@ +/* + * 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.Objects; + +/** + * Argument class of {@link MinioAsyncClient#ListObjectVersions} and {@link + * MinioClient#ListObjectVersions}. + */ +public class ListObjectVersionsArgs extends BucketArgs { + private String delimiter; + private String encodingType; + private Integer maxKeys; + private String prefix; + private String keyMarker; + private String versionIdMarker; + + public String delimiter() { + return delimiter; + } + + public String encodingType() { + return encodingType; + } + + public int maxKeys() { + return maxKeys; + } + + public String prefix() { + return prefix; + } + + public String keyMarker() { + return keyMarker; + } + + public String versionIdMarker() { + return versionIdMarker; + } + + public static Builder builder() { + return new Builder(); + } + + /** Argument builder of {@link ListObjectVersionsArgs}. */ + public static final class Builder extends BucketArgs.Builder { + public Builder delimiter(String delimiter) { + operations.add(args -> args.delimiter = delimiter); + return this; + } + + public Builder encodingType(String encodingType) { + operations.add(args -> args.encodingType = encodingType); + return this; + } + + public Builder maxKeys(Integer maxKeys) { + if (maxKeys != null && maxKeys < 1) { + throw new IllegalArgumentException("valid max keys must be provided"); + } + + operations.add(args -> args.maxKeys = maxKeys); + return this; + } + + public Builder prefix(String prefix) { + operations.add(args -> args.prefix = prefix); + return this; + } + + public Builder keyMarker(String keyMarker) { + operations.add(args -> args.keyMarker = keyMarker); + return this; + } + + public Builder versionIdMarker(String versionIdMarker) { + operations.add(args -> args.versionIdMarker = versionIdMarker); + return this; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ListObjectVersionsArgs)) return false; + if (!super.equals(o)) return false; + ListObjectVersionsArgs that = (ListObjectVersionsArgs) o; + return Objects.equals(delimiter, that.delimiter) + && Objects.equals(encodingType, that.encodingType) + && Objects.equals(maxKeys, that.maxKeys) + && Objects.equals(prefix, that.prefix) + && Objects.equals(keyMarker, that.keyMarker) + && Objects.equals(versionIdMarker, that.versionIdMarker); + } + + @Override + public int hashCode() { + return Objects.hash( + super.hashCode(), delimiter, encodingType, maxKeys, prefix, keyMarker, versionIdMarker); + } +} diff --git a/api/src/main/java/io/minio/ListObjectsArgs.java b/api/src/main/java/io/minio/ListObjectsArgs.java index 40231b6c3..1f4fda6c9 100644 --- a/api/src/main/java/io/minio/ListObjectsArgs.java +++ b/api/src/main/java/io/minio/ListObjectsArgs.java @@ -128,7 +128,7 @@ public Builder useUrlEncodingType(boolean flag) { } public Builder keyMarker(String keyMarker) { - validateNullOrNotEmptyString(keyMarker, "key marker"); + Utils.validateNullOrNotEmptyString(keyMarker, "key marker"); operations.add(args -> args.keyMarker = keyMarker); return this; } @@ -158,7 +158,7 @@ public Builder prefix(String prefix) { } public Builder continuationToken(String continuationToken) { - validateNullOrNotEmptyString(continuationToken, "continuation token"); + Utils.validateNullOrNotEmptyString(continuationToken, "continuation token"); operations.add(args -> args.continuationToken = continuationToken); return this; } @@ -169,7 +169,7 @@ public Builder fetchOwner(boolean fetchOwner) { } public Builder versionIdMarker(String versionIdMarker) { - validateNullOrNotEmptyString(versionIdMarker, "version ID marker"); + Utils.validateNullOrNotEmptyString(versionIdMarker, "version ID marker"); operations.add(args -> args.versionIdMarker = versionIdMarker); return this; } diff --git a/api/src/main/java/io/minio/ListObjectsV1Args.java b/api/src/main/java/io/minio/ListObjectsV1Args.java new file mode 100644 index 000000000..ec75afa60 --- /dev/null +++ b/api/src/main/java/io/minio/ListObjectsV1Args.java @@ -0,0 +1,104 @@ +/* + * 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.Objects; + +/** + * Argument class of {@link MinioAsyncClient#listObjectsV1} and {@link MinioClient#listObjectsV1}. + */ +public class ListObjectsV1Args extends BucketArgs { + private String delimiter; + private String encodingType; + private Integer maxKeys; + private String prefix; + private String marker; + + public String delimiter() { + return delimiter; + } + + public String encodingType() { + return encodingType; + } + + public int maxKeys() { + return maxKeys; + } + + public String prefix() { + return prefix; + } + + public String marker() { + return marker; + } + + public static Builder builder() { + return new Builder(); + } + + /** Argument builder of {@link ListObjectsV1Args}. */ + public static final class Builder extends BucketArgs.Builder { + public Builder delimiter(String delimiter) { + operations.add(args -> args.delimiter = delimiter); + return this; + } + + public Builder encodingType(String encodingType) { + operations.add(args -> args.encodingType = encodingType); + return this; + } + + public Builder maxKeys(Integer maxKeys) { + if (maxKeys != null && maxKeys < 1) { + throw new IllegalArgumentException("valid max keys must be provided"); + } + + operations.add(args -> args.maxKeys = maxKeys); + return this; + } + + public Builder prefix(String prefix) { + operations.add(args -> args.prefix = prefix); + return this; + } + + public Builder marker(String marker) { + operations.add(args -> args.marker = marker); + return this; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ListObjectsV1Args)) return false; + if (!super.equals(o)) return false; + ListObjectsV1Args that = (ListObjectsV1Args) o; + return Objects.equals(delimiter, that.delimiter) + && Objects.equals(encodingType, that.encodingType) + && Objects.equals(maxKeys, that.maxKeys) + && Objects.equals(prefix, that.prefix) + && Objects.equals(marker, that.marker); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), delimiter, encodingType, maxKeys, prefix, marker); + } +} diff --git a/api/src/main/java/io/minio/ListObjectsV2Args.java b/api/src/main/java/io/minio/ListObjectsV2Args.java new file mode 100644 index 000000000..d0b5edd66 --- /dev/null +++ b/api/src/main/java/io/minio/ListObjectsV2Args.java @@ -0,0 +1,157 @@ +/* + * 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.Objects; + +/** + * Argument class of {@link MinioAsyncClient#listObjectsV2} and {@link MinioClient#listObjectsV2}. + */ +public class ListObjectsV2Args extends BucketArgs { + private String delimiter; + private String encodingType; + private Integer maxKeys; + private String prefix; + private String startAfter; + private String continuationToken; + private boolean fetchOwner; + private boolean includeUserMetadata; + + public ListObjectsV2Args(ListObjectsArgs args) { + this.delimiter = args.delimiter(); + this.encodingType = args.useUrlEncodingType() ? "url" : null; + this.maxKeys = args.maxKeys(); + this.prefix = args.prefix(); + this.startAfter = args.startAfter(); + this.continuationToken = args.continuationToken(); + this.fetchOwner = args.fetchOwner(); + this.includeUserMetadata = args.includeUserMetadata(); + } + + public String delimiter() { + return delimiter; + } + + public String encodingType() { + return encodingType; + } + + public int maxKeys() { + return maxKeys; + } + + public String prefix() { + return prefix; + } + + public String startAfter() { + return startAfter; + } + + public String continuationToken() { + return continuationToken; + } + + public boolean fetchOwner() { + return fetchOwner; + } + + public boolean includeUserMetadata() { + return includeUserMetadata; + } + + public static Builder builder() { + return new Builder(); + } + + /** Argument builder of {@link ListObjectsV2Args}. */ + public static final class Builder extends BucketArgs.Builder { + public Builder delimiter(String delimiter) { + operations.add(args -> args.delimiter = delimiter); + return this; + } + + public Builder encodingType(String encodingType) { + operations.add(args -> args.encodingType = encodingType); + return this; + } + + public Builder maxKeys(Integer maxKeys) { + if (maxKeys != null && maxKeys < 1) { + throw new IllegalArgumentException("valid max keys must be provided"); + } + + operations.add(args -> args.maxKeys = maxKeys); + return this; + } + + public Builder prefix(String prefix) { + operations.add(args -> args.prefix = prefix); + return this; + } + + public Builder startAfter(String startAfter) { + operations.add(args -> args.startAfter = startAfter); + return this; + } + + public Builder continuationToken(String continuationToken) { + operations.add(args -> args.continuationToken = continuationToken); + return this; + } + + public Builder fetchOwner(boolean fetchOwner) { + operations.add(args -> args.fetchOwner = fetchOwner); + return this; + } + + public Builder includeUserMetadata(boolean includeUserMetadata) { + operations.add(args -> args.includeUserMetadata = includeUserMetadata); + return this; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ListObjectsV2Args)) return false; + if (!super.equals(o)) return false; + ListObjectsV2Args that = (ListObjectsV2Args) o; + return Objects.equals(delimiter, that.delimiter) + && Objects.equals(encodingType, that.encodingType) + && Objects.equals(maxKeys, that.maxKeys) + && Objects.equals(prefix, that.prefix) + && Objects.equals(startAfter, that.startAfter) + && Objects.equals(continuationToken, that.continuationToken) + && fetchOwner == that.fetchOwner + && includeUserMetadata == that.includeUserMetadata; + } + + @Override + public int hashCode() { + return Objects.hash( + super.hashCode(), + delimiter, + encodingType, + maxKeys, + prefix, + startAfter, + continuationToken, + fetchOwner, + includeUserMetadata); + } +} diff --git a/api/src/main/java/io/minio/ListPartsArgs.java b/api/src/main/java/io/minio/ListPartsArgs.java new file mode 100644 index 000000000..1fcbe9581 --- /dev/null +++ b/api/src/main/java/io/minio/ListPartsArgs.java @@ -0,0 +1,84 @@ +/* + * 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.Objects; + +/** Argument class of {@link MinioAsyncClient#listParts} and {@link MinioClient#listParts}. */ +public class ListPartsArgs extends ObjectArgs { + private String uploadId; + private Integer maxParts; + private Integer partNumberMarker; + + public String uploadId() { + return uploadId; + } + + public Integer maxParts() { + return maxParts; + } + + public Integer partNumberMarker() { + return partNumberMarker; + } + + public static Builder builder() { + return new Builder(); + } + + /** Argument builder of {@link ListPartsArgs}. */ + public static final class Builder extends BucketArgs.Builder { + public Builder uploadId(String uploadId) { + Utils.validateNotEmptyString(uploadId, "upload ID"); + operations.add(args -> args.uploadId = uploadId); + return this; + } + + public Builder maxParts(Integer maxParts) { + if (maxParts != null && maxParts < 1) { + throw new IllegalArgumentException("valid max parts must be provided"); + } + + operations.add(args -> args.maxParts = maxParts); + return this; + } + + public Builder partNumberMarker(Integer partNumberMarker) { + if (partNumberMarker != null && (partNumberMarker < 1 || partNumberMarker > 10000)) { + throw new IllegalArgumentException("valid part number marker must be provided"); + } + operations.add(args -> args.partNumberMarker = partNumberMarker); + return this; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ListPartsArgs)) return false; + if (!super.equals(o)) return false; + ListPartsArgs that = (ListPartsArgs) o; + return Objects.equals(uploadId, that.uploadId) + && Objects.equals(maxParts, that.maxParts) + && Objects.equals(partNumberMarker, that.partNumberMarker); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), uploadId, maxParts, partNumberMarker); + } +} diff --git a/api/src/main/java/io/minio/ListenBucketNotificationArgs.java b/api/src/main/java/io/minio/ListenBucketNotificationArgs.java index a1dc359bc..3240828ed 100644 --- a/api/src/main/java/io/minio/ListenBucketNotificationArgs.java +++ b/api/src/main/java/io/minio/ListenBucketNotificationArgs.java @@ -48,7 +48,7 @@ public static Builder builder() { public static final class Builder extends BucketArgs.Builder { private void validateEvents(String[] events) { - validateNotNull(events, "events"); + Utils.validateNotNull(events, "events"); } protected void validate(ListenBucketNotificationArgs args) { diff --git a/api/src/main/java/io/minio/MinioAsyncClient.java b/api/src/main/java/io/minio/MinioAsyncClient.java index f72e52622..0d4c0b2c5 100644 --- a/api/src/main/java/io/minio/MinioAsyncClient.java +++ b/api/src/main/java/io/minio/MinioAsyncClient.java @@ -32,15 +32,12 @@ import io.minio.errors.InvalidResponseException; import io.minio.errors.ServerException; import io.minio.errors.XmlParserException; -import io.minio.http.HttpUtils; -import io.minio.http.Method; import io.minio.messages.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.DeleteRequest; +import io.minio.messages.DeleteResult; import io.minio.messages.GetObjectAttributesOutput; import io.minio.messages.Item; import io.minio.messages.LegalHold; @@ -139,27 +136,105 @@ * .build(); * } */ -public class MinioAsyncClient extends S3Base { +public class MinioAsyncClient extends BaseS3Client { + /** Argument builder of {@link MinioAsyncClient}. */ + public static final class Builder { + private Http.BaseUrl baseUrl; + private String region; + private Provider provider; + private OkHttpClient httpClient; + private boolean closeHttpClient; + + public Builder baseUrl(Http.BaseUrl baseUrl) { + if (baseUrl.region() == null) { + baseUrl.setRegion(region); + } + region = null; + this.baseUrl = baseUrl; + return this; + } + + public Builder endpoint(String endpoint) { + return this.baseUrl(new Http.BaseUrl(endpoint)); + } + + public Builder endpoint(String endpoint, int port, boolean secure) { + return this.baseUrl(new Http.BaseUrl(endpoint, port, secure)); + } + + public Builder endpoint(URL url) { + return this.baseUrl(new Http.BaseUrl(url)); + } + + public Builder endpoint(HttpUrl url) { + return this.baseUrl(new Http.BaseUrl(url)); + } + + public Builder region(String region) { + if (region != null && !Utils.REGION_REGEX.matcher(region).find()) { + throw new IllegalArgumentException("invalid region " + region); + } + if (baseUrl != null) { + baseUrl.setRegion(region); + } else { + this.region = region; + } + return this; + } + + public Builder credentials(String accessKey, String secretKey) { + provider = new StaticProvider(accessKey, secretKey, null); + return this; + } + + public Builder credentialsProvider(Provider provider) { + this.provider = provider; + return this; + } + + public Builder httpClient(OkHttpClient httpClient) { + Utils.validateNotNull(httpClient, "http client"); + this.httpClient = httpClient; + return this; + } + + public Builder httpClient(OkHttpClient httpClient, boolean close) { + Utils.validateNotNull(httpClient, "http client"); + this.httpClient = httpClient; + this.closeHttpClient = close; + return this; + } + + public MinioAsyncClient build() { + Utils.validateNotNull(baseUrl, "endpoint"); + + if (baseUrl.awsDomainSuffix() != null + && baseUrl.awsDomainSuffix().endsWith(".cn") + && !baseUrl.awsS3Prefix().endsWith("s3-accelerate.") + && baseUrl.region() == null) { + throw new IllegalArgumentException( + "Region missing in Amazon S3 China endpoint " + baseUrl); + } + + if (httpClient == null) { + closeHttpClient = true; + httpClient = Http.newDefaultClient(); + } + + return new MinioAsyncClient(baseUrl, provider, httpClient, closeHttpClient); + } + } + + public static Builder builder() { + return new Builder(); + } + private MinioAsyncClient( - HttpUrl baseUrl, - String awsS3Prefix, - String awsDomainSuffix, - boolean awsDualstack, - boolean useVirtualStyle, - String region, + Http.BaseUrl baseUrl, Provider provider, OkHttpClient httpClient, boolean closeHttpClient) { - super( - baseUrl, - awsS3Prefix, - awsDomainSuffix, - awsDualstack, - useVirtualStyle, - region, - provider, - httpClient, - closeHttpClient); + super(baseUrl, provider, httpClient, closeHttpClient); } protected MinioAsyncClient(MinioAsyncClient client) { @@ -252,7 +327,7 @@ public CompletableFuture getObject(GetObjectArgs args) return executeGetAsync( args, args.getHeaders(), - (args.versionId() != null) ? newMultimap("versionId", args.versionId()) : null) + (args.versionId() != null) ? Utils.newMultimap("versionId", args.versionId()) : null) .thenApply( response -> { return new GetObjectResponse( @@ -274,7 +349,7 @@ private void downloadObject( try { Path filePath = Paths.get(filename); String tempFilename = - filename + "." + S3Escaper.encode(statObjectResponse.etag()) + ".part.minio"; + filename + "." + Utils.encode(statObjectResponse.etag()) + ".part.minio"; Path tempFilePath = Paths.get(tempFilename); if (Files.exists(tempFilePath)) Files.delete(tempFilePath); os = Files.newOutputStream(tempFilePath, StandardOpenOption.CREATE, StandardOpenOption.WRITE); @@ -674,7 +749,8 @@ public CompletableFuture composeObject(ComposeObjectArgs ar CompletableFuture completableFuture = CompletableFuture.supplyAsync( () -> { - Multimap headers = newMultimap(args.extraHeaders()); + Multimap headers = + Utils.newMultimap(args.extraHeaders()); headers.putAll(args.genHeaders()); return headers; }) @@ -706,8 +782,8 @@ public CompletableFuture composeObject(ComposeObjectArgs ar uploadId -> { Multimap ssecHeaders = HashMultimap.create(); if (args.sse() != null - && args.sse() instanceof ServerSideEncryptionCustomerKey) { - ssecHeaders.putAll(newMultimap(args.sse().headers())); + && args.sse() instanceof ServerSideEncryption.CustomerKey) { + ssecHeaders.putAll(Utils.newMultimap(args.sse().headers())); } int partNumber = 0; @@ -733,7 +809,7 @@ public CompletableFuture composeObject(ComposeObjectArgs ar final Multimap headers; try { - headers = newMultimap(src.headers()); + headers = Utils.newMultimap(src.headers()); } catch (InternalException e) { throw new CompletionException(e); } @@ -785,7 +861,7 @@ public CompletableFuture composeObject(ComposeObjectArgs ar } long endBytes = offset + length - 1; - Multimap headersCopy = newMultimap(headers); + Multimap headersCopy = Utils.newMultimap(headers); headersCopy.put( "x-amz-copy-source-range", "bytes=" + offset + "-" + endBytes); @@ -877,7 +953,7 @@ public CompletableFuture composeObject(ComposeObjectArgs ar * String url = * minioAsyncClient.getPresignedObjectUrl( * GetPresignedObjectUrlArgs.builder() - * .method(Method.DELETE) + * .method(Http.Method.DELETE) * .bucket("my-bucketname") * .object("my-objectname") * .expiry(24 * 60 * 60) @@ -892,7 +968,7 @@ public CompletableFuture composeObject(ComposeObjectArgs ar * String url = * minioAsyncClient.getPresignedObjectUrl( * GetPresignedObjectUrlArgs.builder() - * .method(Method.PUT) + * .method(Http.Method.PUT) * .bucket("my-bucketname") * .object("my-objectname") * .expiry(1, TimeUnit.DAYS) @@ -905,7 +981,7 @@ public CompletableFuture composeObject(ComposeObjectArgs ar * String url = * minioAsyncClient.getPresignedObjectUrl( * GetPresignedObjectUrlArgs.builder() - * .method(Method.GET) + * .method(Http.Method.GET) * .bucket("my-bucketname") * .object("my-objectname") * .expiry(2, TimeUnit.HOURS) @@ -933,9 +1009,11 @@ public String getPresignedObjectUrl(GetPresignedObjectUrlArgs args) checkArgs(args); byte[] body = - (args.method() == Method.PUT || args.method() == Method.POST) ? HttpUtils.EMPTY_BODY : null; + (args.method() == Http.Method.PUT || args.method() == Http.Method.POST) + ? Utils.EMPTY_BODY + : null; - Multimap queryParams = newMultimap(args.extraQueryParams()); + Multimap queryParams = Utils.newMultimap(args.extraQueryParams()); if (args.versionId() != null) queryParams.put("versionId", args.versionId()); String region = null; @@ -959,7 +1037,7 @@ public String getPresignedObjectUrl(GetPresignedObjectUrlArgs args) createRequest( url, args.method(), - args.extraHeaders() == null ? null : httpHeaders(args.extraHeaders()), + args.extraHeaders() == null ? null : Utils.httpHeaders(args.extraHeaders()), body, 0, creds); @@ -1090,9 +1168,9 @@ public CompletableFuture removeObject(RemoveObjectArgs args) return executeDeleteAsync( args, args.bypassGovernanceMode() - ? newMultimap("x-amz-bypass-governance-retention", "true") + ? Utils.newMultimap("x-amz-bypass-governance-retention", "true") : null, - (args.versionId() != null) ? newMultimap("versionId", args.versionId()) : null) + (args.versionId() != null) ? Utils.newMultimap("versionId", args.versionId()) : null) .thenAccept(response -> response.close()); } @@ -1105,35 +1183,36 @@ public CompletableFuture removeObject(RemoveObjectArgs args) * objects.add(new DeleteObject("my-objectname1")); * objects.add(new DeleteObject("my-objectname2")); * objects.add(new DeleteObject("my-objectname3")); - * Iterable> results = + * Iterable> results = * minioAsyncClient.removeObjects( * RemoveObjectsArgs.builder().bucket("my-bucketname").objects(objects).build()); - * for (Result result : results) { - * DeleteError error = result.get(); + * for (Result result : results) { + * DeleteResult.Error error = result.get(); * System.out.println( * "Error in deleting object " + error.objectName() + "; " + error.message()); * } * } * * @param args {@link RemoveObjectsArgs} object. - * @return {@code Iterable>} - Lazy iterator contains object removal status. + * @return {@code Iterable>} - Lazy iterator contains object removal + * status. */ - public Iterable> removeObjects(RemoveObjectsArgs args) { + public Iterable> removeObjects(RemoveObjectsArgs args) { checkArgs(args); - return new Iterable>() { + return new Iterable>() { @Override - public Iterator> iterator() { - return new Iterator>() { - private Result error = null; - private Iterator errorIterator = null; + public Iterator> iterator() { + return new Iterator>() { + private Result error = null; + private Iterator errorIterator = null; private boolean completed = false; - private Iterator objectIter = args.objects().iterator(); + private Iterator objectIter = args.objects().iterator(); private void setError() { error = null; while (errorIterator.hasNext()) { - DeleteError deleteError = errorIterator.next(); + DeleteResult.Error deleteError = errorIterator.next(); if (!"NoSuchVersion".equals(deleteError.code())) { error = new Result<>(deleteError); break; @@ -1147,7 +1226,7 @@ private synchronized void populate() { } try { - List objectList = new LinkedList<>(); + List objectList = new LinkedList<>(); while (objectIter.hasNext() && objectList.size() < 1000) { objectList.add(objectIter.next()); } @@ -1171,8 +1250,8 @@ private synchronized void populate() { } catch (ExecutionException e) { throwEncapsulatedException(e); } - if (!response.result().errorList().isEmpty()) { - errorIterator = response.result().errorList().iterator(); + if (!response.result().errors().isEmpty()) { + errorIterator = response.result().errors().iterator(); setError(); completed = true; } @@ -1205,11 +1284,11 @@ public boolean hasNext() { } @Override - public Result next() { + public Result next() { if (!hasNext()) throw new NoSuchElementException(); if (this.error != null) { - Result error = this.error; + Result error = this.error; this.error = null; return error; } @@ -1262,7 +1341,7 @@ public CompletableFuture restoreObject(RestoreObjectArgs args) throws InsufficientDataException, InternalException, InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { checkArgs(args); - return executePostAsync(args, null, newMultimap("restore", ""), args.request()) + return executePostAsync(args, null, Utils.newMultimap("restore", ""), args.request()) .thenAccept(response -> response.close()); } @@ -1325,10 +1404,11 @@ public Iterable> listObjects(ListObjectsArgs args) { * Lists bucket information of all buckets. * *
Example:{@code
-   * CompletableFuture> future = minioAsyncClient.listBuckets();
+   * CompletableFuture> future = minioAsyncClient.listBuckets();
    * }
* - * @return {@link CompletableFuture}<{@link List}<{@link Bucket}>> object. + * @return {@link CompletableFuture}<{@link List}<{@link + * ListAllMyBucketsResult.Bucket}>> object. * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. * @throws InternalException thrown to indicate internal library error. * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. @@ -1336,7 +1416,7 @@ public Iterable> listObjects(ListObjectsArgs args) { * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. * @throws XmlParserException thrown to indicate XML parsing error. */ - public CompletableFuture> listBuckets() + public CompletableFuture> listBuckets() throws InsufficientDataException, InternalException, InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { return listBucketsAsync(null, null, null, null, null, null) @@ -1350,30 +1430,31 @@ public CompletableFuture> listBuckets() * Lists bucket information of all buckets. * *
Example:{@code
-   * Iterable> results = minioAsyncClient.listBuckets(ListBucketsArgs.builder().build());
-   * for (Result result : results) {
+   * Iterable> results = minioAsyncClient.listBuckets(ListBucketsArgs.builder().build());
+   * for (Result result : results) {
    *   Bucket bucket = result.get();
    *   System.out.println(String.format("Bucket: %s, Region: %s, CreationDate: %s", bucket.name(), bucket.bucketRegion(), bucket.creationDate()));
    * }
    * }
* - * @return {@link Iterable}<{@link List}<{@link Bucket}>> object. + * @return {@link Iterable}<{@link List}<{@link ListAllMyBucketsResult.Bucket}>> + * object. */ - public Iterable> listBuckets(ListBucketsArgs args) { - return new Iterable>() { + public Iterable> listBuckets(ListBucketsArgs args) { + return new Iterable>() { @Override - public Iterator> iterator() { - return new Iterator>() { + public Iterator> iterator() { + return new Iterator>() { private ListAllMyBucketsResult result = null; - private Result error = null; - private Iterator iterator = null; + private Result error = null; + private Iterator iterator = null; private boolean completed = false; private synchronized void populate() { if (completed) return; try { - this.iterator = new LinkedList().iterator(); + this.iterator = new LinkedList().iterator(); try { ListBucketsResponse response = listBucketsAsync( @@ -1430,7 +1511,7 @@ public boolean hasNext() { } @Override - public Result next() { + public Result next() { if (this.completed) throw new NoSuchElementException(); if (this.error == null && this.iterator == null) { populate(); @@ -1448,7 +1529,7 @@ public Result next() { return this.error; } - Bucket item = null; + ListAllMyBucketsResult.Bucket item = null; if (this.iterator.hasNext()) { item = this.iterator.next(); } @@ -1572,21 +1653,21 @@ public CompletableFuture makeBucket(MakeBucketArgs args) } if (region == null) { - region = US_EAST_1; + region = Http.US_EAST_1; } Multimap headers = - args.objectLock() ? newMultimap("x-amz-bucket-object-lock-enabled", "true") : null; + args.objectLock() ? Utils.newMultimap("x-amz-bucket-object-lock-enabled", "true") : null; final String location = region; return executeAsync( - Method.PUT, + Http.Method.PUT, args.bucket(), null, location, - httpHeaders(merge(args.extraHeaders(), headers)), + Utils.httpHeaders(Utils.mergeMultimap(args.extraHeaders(), headers)), args.extraQueryParams(), - location.equals(US_EAST_1) ? null : new CreateBucketConfiguration(location), + location.equals(Http.US_EAST_1) ? null : new CreateBucketConfiguration(location), 0) .thenAccept( response -> { @@ -1616,7 +1697,7 @@ public CompletableFuture setBucketVersioning(SetBucketVersioningArgs args) throws InsufficientDataException, InternalException, InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { checkArgs(args); - return executePutAsync(args, null, newMultimap("versioning", ""), args.config(), 0) + return executePutAsync(args, null, Utils.newMultimap("versioning", ""), args.config(), 0) .thenAccept(response -> response.close()); } @@ -1643,7 +1724,7 @@ public CompletableFuture getBucketVersioning( throws InsufficientDataException, InternalException, InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { checkArgs(args); - return executeGetAsync(args, null, newMultimap("versioning", "")) + return executeGetAsync(args, null, Utils.newMultimap("versioning", "")) .thenApply( response -> { try { @@ -1679,7 +1760,7 @@ public CompletableFuture setObjectLockConfiguration(SetObjectLockConfigura throws InsufficientDataException, InternalException, InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { checkArgs(args); - return executePutAsync(args, null, newMultimap("object-lock", ""), args.config(), 0) + return executePutAsync(args, null, Utils.newMultimap("object-lock", ""), args.config(), 0) .thenAccept(response -> response.close()); } @@ -1706,7 +1787,7 @@ public CompletableFuture deleteObjectLockConfiguration( NoSuchAlgorithmException, XmlParserException { checkArgs(args); return executePutAsync( - args, null, newMultimap("object-lock", ""), new ObjectLockConfiguration(), 0) + args, null, Utils.newMultimap("object-lock", ""), new ObjectLockConfiguration(), 0) .thenAccept(response -> response.close()); } @@ -1733,7 +1814,7 @@ public CompletableFuture getObjectLockConfiguration( throws InsufficientDataException, InternalException, InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { checkArgs(args); - return executeGetAsync(args, null, newMultimap("object-lock", "")) + return executeGetAsync(args, null, Utils.newMultimap("object-lock", "")) .thenApply( response -> { try { @@ -1774,12 +1855,12 @@ public CompletableFuture setObjectRetention(SetObjectRetentionArgs args) throws InsufficientDataException, InternalException, InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { checkArgs(args); - Multimap queryParams = newMultimap("retention", ""); + Multimap queryParams = Utils.newMultimap("retention", ""); if (args.versionId() != null) queryParams.put("versionId", args.versionId()); return executePutAsync( args, args.bypassGovernanceMode() - ? newMultimap("x-amz-bypass-governance-retention", "True") + ? Utils.newMultimap("x-amz-bypass-governance-retention", "True") : null, queryParams, args.config(), @@ -1812,7 +1893,7 @@ public CompletableFuture getObjectRetention(GetObjectRetentionArgs ar throws InsufficientDataException, InternalException, InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { checkArgs(args); - Multimap queryParams = newMultimap("retention", ""); + Multimap queryParams = Utils.newMultimap("retention", ""); if (args.versionId() != null) queryParams.put("versionId", args.versionId()); return executeGetAsync(args, null, queryParams) .exceptionally( @@ -1875,7 +1956,7 @@ public CompletableFuture enableObjectLegalHold(EnableObjectLegalHoldArgs a throws InsufficientDataException, InternalException, InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { checkArgs(args); - Multimap queryParams = newMultimap("legal-hold", ""); + Multimap queryParams = Utils.newMultimap("legal-hold", ""); if (args.versionId() != null) queryParams.put("versionId", args.versionId()); return executePutAsync(args, null, queryParams, new LegalHold(true), 0) .thenAccept(response -> response.close()); @@ -1906,7 +1987,7 @@ public CompletableFuture disableObjectLegalHold(DisableObjectLegalHoldArgs throws InsufficientDataException, InternalException, InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { checkArgs(args); - Multimap queryParams = newMultimap("legal-hold", ""); + Multimap queryParams = Utils.newMultimap("legal-hold", ""); if (args.versionId() != null) queryParams.put("versionId", args.versionId()); return executePutAsync(args, null, queryParams, new LegalHold(false), 0) .thenAccept(response -> response.close()); @@ -1938,7 +2019,7 @@ public CompletableFuture isObjectLegalHoldEnabled(IsObjectLegalHoldEnab throws InsufficientDataException, InternalException, InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { checkArgs(args); - Multimap queryParams = newMultimap("legal-hold", ""); + Multimap queryParams = Utils.newMultimap("legal-hold", ""); if (args.versionId() != null) queryParams.put("versionId", args.versionId()); return executeGetAsync(args, null, queryParams) .exceptionally( @@ -2158,7 +2239,7 @@ public CompletableFuture getBucketPolicy(GetBucketPolicyArgs args) throws InsufficientDataException, InternalException, InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { checkArgs(args); - return executeGetAsync(args, null, newMultimap("policy", "")) + return executeGetAsync(args, null, Utils.newMultimap("policy", "")) .exceptionally( e -> { Throwable ex = e.getCause(); @@ -2264,8 +2345,8 @@ public CompletableFuture setBucketPolicy(SetBucketPolicyArgs args) checkArgs(args); return executePutAsync( args, - newMultimap("Content-Type", "application/json"), - newMultimap("policy", ""), + Utils.newMultimap("Content-Type", "application/json"), + Utils.newMultimap("policy", ""), args.config(), 0) .thenAccept(response -> response.close()); @@ -2293,7 +2374,7 @@ public CompletableFuture deleteBucketPolicy(DeleteBucketPolicyArgs args) throws InsufficientDataException, InternalException, InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { checkArgs(args); - return executeDeleteAsync(args, null, newMultimap("policy", "")) + return executeDeleteAsync(args, null, Utils.newMultimap("policy", "")) .exceptionally( e -> { Throwable ex = e.getCause(); @@ -2355,7 +2436,7 @@ public CompletableFuture setBucketLifecycle(SetBucketLifecycleArgs args) throws InsufficientDataException, InternalException, InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { checkArgs(args); - return executePutAsync(args, null, newMultimap("lifecycle", ""), args.config(), 0) + return executePutAsync(args, null, Utils.newMultimap("lifecycle", ""), args.config(), 0) .thenAccept(response -> response.close()); } @@ -2380,7 +2461,7 @@ public CompletableFuture deleteBucketLifecycle(DeleteBucketLifecycleArgs a throws InsufficientDataException, InternalException, InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { checkArgs(args); - return executeDeleteAsync(args, null, newMultimap("lifecycle", "")) + return executeDeleteAsync(args, null, Utils.newMultimap("lifecycle", "")) .thenAccept(response -> response.close()); } @@ -2407,7 +2488,7 @@ public CompletableFuture getBucketLifecycle(GetBucketLif throws InsufficientDataException, InternalException, InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { checkArgs(args); - return executeGetAsync(args, null, newMultimap("lifecycle", "")) + return executeGetAsync(args, null, Utils.newMultimap("lifecycle", "")) .exceptionally( e -> { Throwable ex = e.getCause(); @@ -2466,7 +2547,7 @@ public CompletableFuture getBucketNotification( throws InsufficientDataException, InternalException, InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { checkArgs(args); - return executeGetAsync(args, null, newMultimap("notification", "")) + return executeGetAsync(args, null, Utils.newMultimap("notification", "")) .thenApply( response -> { try { @@ -2516,7 +2597,7 @@ public CompletableFuture setBucketNotification(SetBucketNotificationArgs a throws InsufficientDataException, InternalException, InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { checkArgs(args); - return executePutAsync(args, null, newMultimap("notification", ""), args.config(), 0) + return executePutAsync(args, null, Utils.newMultimap("notification", ""), args.config(), 0) .thenAccept(response -> response.close()); } @@ -2542,7 +2623,11 @@ public CompletableFuture deleteBucketNotification(DeleteBucketNotification NoSuchAlgorithmException, XmlParserException { checkArgs(args); return executePutAsync( - args, null, newMultimap("notification", ""), new NotificationConfiguration(), 0) + args, + null, + Utils.newMultimap("notification", ""), + new NotificationConfiguration(null, null, null, null), + 0) .thenAccept(response -> response.close()); } @@ -2569,7 +2654,7 @@ public CompletableFuture getBucketReplication( throws InsufficientDataException, InternalException, InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { checkArgs(args); - return executeGetAsync(args, null, newMultimap("replication", "")) + return executeGetAsync(args, null, Utils.newMultimap("replication", "")) .exceptionally( e -> { Throwable ex = e.getCause(); @@ -2652,9 +2737,9 @@ public CompletableFuture setBucketReplication(SetBucketReplicationArgs arg return executePutAsync( args, (args.objectLockToken() != null) - ? newMultimap("x-amz-bucket-object-lock-token", args.objectLockToken()) + ? Utils.newMultimap("x-amz-bucket-object-lock-token", args.objectLockToken()) : null, - newMultimap("replication", ""), + Utils.newMultimap("replication", ""), args.config(), 0) .thenAccept(response -> response.close()); @@ -2681,7 +2766,7 @@ public CompletableFuture deleteBucketReplication(DeleteBucketReplicationAr throws InsufficientDataException, InternalException, InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { checkArgs(args); - return executeDeleteAsync(args, null, newMultimap("replication", "")) + return executeDeleteAsync(args, null, Utils.newMultimap("replication", "")) .thenAccept(response -> response.close()); } @@ -2732,7 +2817,7 @@ public CloseableIterator> listenBucketNotification( checkArgs(args); Multimap queryParams = - newMultimap("prefix", args.prefix(), "suffix", args.suffix()); + Utils.newMultimap("prefix", args.prefix(), "suffix", args.suffix()); for (String event : args.events()) { queryParams.put("events", event); } @@ -2805,8 +2890,8 @@ public SelectResponseStream selectObjectContent(SelectObjectContentArgs args) response = executePostAsync( args, - (args.ssec() != null) ? newMultimap(args.ssec().headers()) : null, - newMultimap("select", "", "select-type", "2"), + (args.ssec() != null) ? Utils.newMultimap(args.ssec().headers()) : null, + Utils.newMultimap("select", "", "select-type", "2"), new SelectObjectContentRequest( args.sqlExpression(), args.requestProgress(), @@ -2844,7 +2929,7 @@ public CompletableFuture setBucketEncryption(SetBucketEncryptionArgs args) throws InsufficientDataException, InternalException, InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { checkArgs(args); - return executePutAsync(args, null, newMultimap("encryption", ""), args.config(), 0) + return executePutAsync(args, null, Utils.newMultimap("encryption", ""), args.config(), 0) .thenAccept(response -> response.close()); } @@ -2870,7 +2955,7 @@ public CompletableFuture getBucketEncryption(GetBucketEncrypti throws InsufficientDataException, InternalException, InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { checkArgs(args); - return executeGetAsync(args, null, newMultimap("encryption", "")) + return executeGetAsync(args, null, Utils.newMultimap("encryption", "")) .exceptionally( e -> { Throwable ex = e.getCause(); @@ -2927,7 +3012,7 @@ public CompletableFuture deleteBucketEncryption(DeleteBucketEncryptionArgs throws InsufficientDataException, InternalException, InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { checkArgs(args); - return executeDeleteAsync(args, null, newMultimap("encryption", "")) + return executeDeleteAsync(args, null, Utils.newMultimap("encryption", "")) .exceptionally( e -> { Throwable ex = e.getCause(); @@ -2977,7 +3062,7 @@ public CompletableFuture getBucketTags(GetBucketTagsArgs args) throws InsufficientDataException, InternalException, InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { checkArgs(args); - return executeGetAsync(args, null, newMultimap("tagging", "")) + return executeGetAsync(args, null, Utils.newMultimap("tagging", "")) .exceptionally( e -> { Throwable ex = e.getCause(); @@ -3034,7 +3119,7 @@ public CompletableFuture setBucketTags(SetBucketTagsArgs args) throws InsufficientDataException, InternalException, InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { checkArgs(args); - return executePutAsync(args, null, newMultimap("tagging", ""), args.tags(), 0) + return executePutAsync(args, null, Utils.newMultimap("tagging", ""), args.tags(), 0) .thenAccept(response -> response.close()); } @@ -3059,7 +3144,7 @@ public CompletableFuture deleteBucketTags(DeleteBucketTagsArgs args) throws InsufficientDataException, InternalException, InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { checkArgs(args); - return executeDeleteAsync(args, null, newMultimap("tagging", "")) + return executeDeleteAsync(args, null, Utils.newMultimap("tagging", "")) .thenAccept(response -> response.close()); } @@ -3085,7 +3170,7 @@ public CompletableFuture getObjectTags(GetObjectTagsArgs args) throws InsufficientDataException, InternalException, InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { checkArgs(args); - Multimap queryParams = newMultimap("tagging", ""); + Multimap queryParams = Utils.newMultimap("tagging", ""); if (args.versionId() != null) queryParams.put("versionId", args.versionId()); return executeGetAsync(args, null, queryParams) .thenApply( @@ -3128,7 +3213,7 @@ public CompletableFuture setObjectTags(SetObjectTagsArgs args) throws InsufficientDataException, InternalException, InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { checkArgs(args); - Multimap queryParams = newMultimap("tagging", ""); + Multimap queryParams = Utils.newMultimap("tagging", ""); if (args.versionId() != null) queryParams.put("versionId", args.versionId()); return executePutAsync(args, null, queryParams, args.tags(), 0) .thenAccept(response -> response.close()); @@ -3155,7 +3240,7 @@ public CompletableFuture deleteObjectTags(DeleteObjectTagsArgs args) throws InsufficientDataException, InternalException, InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { checkArgs(args); - Multimap queryParams = newMultimap("tagging", ""); + Multimap queryParams = Utils.newMultimap("tagging", ""); if (args.versionId() != null) queryParams.put("versionId", args.versionId()); return executeDeleteAsync(args, null, queryParams).thenAccept(response -> response.close()); } @@ -3181,7 +3266,7 @@ public CompletableFuture getBucketCors(GetBucketCorsArgs args throws InsufficientDataException, InternalException, InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { checkArgs(args); - return executeGetAsync(args, null, newMultimap("cors", "")) + return executeGetAsync(args, null, Utils.newMultimap("cors", "")) .exceptionally( e -> { Throwable ex = e.getCause(); @@ -3258,7 +3343,7 @@ public CompletableFuture setBucketCors(SetBucketCorsArgs args) throws InsufficientDataException, InternalException, InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { checkArgs(args); - return executePutAsync(args, null, newMultimap("cors", ""), args.config(), 0) + return executePutAsync(args, null, Utils.newMultimap("cors", ""), args.config(), 0) .thenAccept(response -> response.close()); } @@ -3283,7 +3368,7 @@ public CompletableFuture deleteBucketCors(DeleteBucketCorsArgs args) throws InsufficientDataException, InternalException, InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { checkArgs(args); - return executeDeleteAsync(args, null, newMultimap("cors", "")) + return executeDeleteAsync(args, null, Utils.newMultimap("cors", "")) .thenAccept(response -> response.close()); } @@ -3309,7 +3394,7 @@ public CompletableFuture getObjectAcl(GetObjectAclArgs args throws InsufficientDataException, InternalException, InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { checkArgs(args); - Multimap queryParams = newMultimap("acl", ""); + Multimap queryParams = Utils.newMultimap("acl", ""); if (args.versionId() != null) queryParams.put("versionId", args.versionId()); return executeGetAsync(args, null, queryParams) .thenApply( @@ -3355,7 +3440,7 @@ public CompletableFuture getObjectAttributes( NoSuchAlgorithmException, XmlParserException { checkArgs(args); - Multimap queryParams = newMultimap("attributes", ""); + Multimap queryParams = Utils.newMultimap("attributes", ""); if (args.versionId() != null) queryParams.put("versionId", args.versionId()); Multimap headers = HashMultimap.create(); @@ -3494,7 +3579,7 @@ public CompletableFuture uploadSnowballObjects( }) .thenCompose( baos -> { - Multimap headers = newMultimap(args.extraHeaders()); + Multimap headers = Utils.newMultimap(args.extraHeaders()); headers.putAll(args.genHeaders()); headers.put("X-Amz-Meta-Snowball-Auto-Extract", "true"); @@ -3617,7 +3702,7 @@ public CompletableFuture putObjectFanOut(PutObjectFanOu multipartBuilder.addFormDataPart( "file", "fanout-content", - new HttpRequestBody(new PartSource(args.stream(), args.size()), null)); + new Http.RequestBody(args.stream(), args.size(), Http.DEFAULT_MEDIA_TYPE)); return multipartBuilder.build(); } catch (JsonProcessingException e) { @@ -3684,9 +3769,11 @@ public CompletableFuture promptObject(PromptObjectArgs arg NoSuchAlgorithmException, XmlParserException { checkArgs(args); - Multimap queryParams = newMultimap("lambdaArn", args.lambdaArn()); + Multimap queryParams = Utils.newMultimap("lambdaArn", args.lambdaArn()); Multimap headers = - merge(newMultimap(args.headers()), newMultimap("Content-Type", "application/json")); + Utils.mergeMultimap( + Utils.newMultimap(args.headers()), + Utils.newMultimap("Content-Type", "application/json")); Map promptArgs = args.promptArgs(); if (promptArgs == null) promptArgs = new HashMap<>(); @@ -3704,164 +3791,4 @@ public CompletableFuture promptObject(PromptObjectArgs arg response.body().byteStream()); }); } - - public static Builder builder() { - return new Builder(); - } - - /** Argument builder of {@link MinioClient}. */ - public static final class Builder { - private HttpUrl baseUrl; - private String awsS3Prefix; - private String awsDomainSuffix; - private boolean awsDualstack; - private boolean useVirtualStyle; - - private String region; - private Provider provider; - private OkHttpClient httpClient; - private boolean closeHttpClient; - - private void setAwsInfo(String host, boolean https) { - this.awsS3Prefix = null; - this.awsDomainSuffix = null; - this.awsDualstack = false; - - if (!HttpUtils.HOSTNAME_REGEX.matcher(host).find()) return; - - if (HttpUtils.AWS_ELB_ENDPOINT_REGEX.matcher(host).find()) { - String[] tokens = host.split("\\.elb\\.amazonaws\\.com", 1)[0].split("\\."); - this.region = tokens[tokens.length - 1]; - return; - } - - if (!HttpUtils.AWS_ENDPOINT_REGEX.matcher(host).find()) return; - - if (!HttpUtils.AWS_S3_ENDPOINT_REGEX.matcher(host).find()) { - throw new IllegalArgumentException("invalid Amazon AWS host " + host); - } - - Matcher matcher = HttpUtils.AWS_S3_PREFIX_REGEX.matcher(host); - matcher.lookingAt(); - int end = matcher.end(); - - this.awsS3Prefix = host.substring(0, end); - if (this.awsS3Prefix.contains("s3-accesspoint") && !https) { - throw new IllegalArgumentException("use HTTPS scheme for host " + host); - } - - String[] tokens = host.substring(end).split("\\."); - awsDualstack = "dualstack".equals(tokens[0]); - if (awsDualstack) tokens = Arrays.copyOfRange(tokens, 1, tokens.length); - String regionInHost = null; - if (!tokens[0].equals("vpce") && !tokens[0].equals("amazonaws")) { - regionInHost = tokens[0]; - tokens = Arrays.copyOfRange(tokens, 1, tokens.length); - } - this.awsDomainSuffix = String.join(".", tokens); - - if (host.equals("s3-external-1.amazonaws.com")) regionInHost = "us-east-1"; - if (host.equals("s3-us-gov-west-1.amazonaws.com") - || host.equals("s3-fips-us-gov-west-1.amazonaws.com")) { - regionInHost = "us-gov-west-1"; - } - - if (regionInHost != null) this.region = regionInHost; - } - - private void setBaseUrl(HttpUrl url) { - this.baseUrl = url; - this.setAwsInfo(url.host(), url.isHttps()); - this.useVirtualStyle = this.awsDomainSuffix != null || url.host().endsWith("aliyuncs.com"); - } - - public Builder endpoint(String endpoint) { - setBaseUrl(HttpUtils.getBaseUrl(endpoint)); - return this; - } - - public Builder endpoint(String endpoint, int port, boolean secure) { - HttpUrl url = HttpUtils.getBaseUrl(endpoint); - if (port < 1 || port > 65535) { - throw new IllegalArgumentException("port must be in range of 1 to 65535"); - } - url = url.newBuilder().port(port).scheme(secure ? "https" : "http").build(); - - setBaseUrl(url); - return this; - } - - public Builder endpoint(URL url) { - HttpUtils.validateNotNull(url, "url"); - return endpoint(HttpUrl.get(url)); - } - - public Builder endpoint(HttpUrl url) { - HttpUtils.validateNotNull(url, "url"); - HttpUtils.validateUrl(url); - setBaseUrl(url); - return this; - } - - public Builder region(String region) { - if (region != null && !HttpUtils.REGION_REGEX.matcher(region).find()) { - throw new IllegalArgumentException("invalid region " + region); - } - this.region = region; - return this; - } - - public Builder credentials(String accessKey, String secretKey) { - this.provider = new StaticProvider(accessKey, secretKey, null); - return this; - } - - public Builder credentialsProvider(Provider provider) { - this.provider = provider; - return this; - } - - public Builder httpClient(OkHttpClient httpClient) { - HttpUtils.validateNotNull(httpClient, "http client"); - this.httpClient = httpClient; - return this; - } - - public Builder httpClient(OkHttpClient httpClient, boolean close) { - HttpUtils.validateNotNull(httpClient, "http client"); - this.httpClient = httpClient; - this.closeHttpClient = close; - return this; - } - - public MinioAsyncClient build() { - HttpUtils.validateNotNull(this.baseUrl, "endpoint"); - - if (this.awsDomainSuffix != null - && this.awsDomainSuffix.endsWith(".cn") - && !this.awsS3Prefix.endsWith("s3-accelerate.") - && this.region == null) { - throw new IllegalArgumentException( - "Region missing in Amazon S3 China endpoint " + this.baseUrl); - } - - if (this.httpClient == null) { - this.closeHttpClient = true; - this.httpClient = - HttpUtils.newDefaultHttpClient( - DEFAULT_CONNECTION_TIMEOUT, DEFAULT_CONNECTION_TIMEOUT, DEFAULT_CONNECTION_TIMEOUT); - } - - return new MinioAsyncClient( - baseUrl, - awsS3Prefix, - awsDomainSuffix, - awsDualstack, - useVirtualStyle, - region, - provider, - httpClient, - closeHttpClient); - } - } } diff --git a/api/src/main/java/io/minio/MinioClient.java b/api/src/main/java/io/minio/MinioClient.java index 3b2bf5e19..f38afc69b 100644 --- a/api/src/main/java/io/minio/MinioClient.java +++ b/api/src/main/java/io/minio/MinioClient.java @@ -17,7 +17,6 @@ package io.minio; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.minio.credentials.Provider; import io.minio.errors.BucketPolicyTooLargeException; import io.minio.errors.ErrorResponseException; @@ -27,11 +26,11 @@ 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.DeleteResult; import io.minio.messages.Item; import io.minio.messages.LifecycleConfiguration; +import io.minio.messages.ListAllMyBucketsResult; import io.minio.messages.NotificationConfiguration; import io.minio.messages.NotificationRecords; import io.minio.messages.ObjectLockConfiguration; @@ -461,7 +460,7 @@ public ObjectWriteResponse composeObject(ComposeObjectArgs args) * String url = * minioClient.getPresignedObjectUrl( * GetPresignedObjectUrlArgs.builder() - * .method(Method.DELETE) + * .method(Http.Method.DELETE) * .bucket("my-bucketname") * .object("my-objectname") * .expiry(24 * 60 * 60) @@ -476,7 +475,7 @@ public ObjectWriteResponse composeObject(ComposeObjectArgs args) * String url = * minioClient.getPresignedObjectUrl( * GetPresignedObjectUrlArgs.builder() - * .method(Method.PUT) + * .method(Http.Method.PUT) * .bucket("my-bucketname") * .object("my-objectname") * .expiry(1, TimeUnit.DAYS) @@ -489,7 +488,7 @@ public ObjectWriteResponse composeObject(ComposeObjectArgs args) * String url = * minioClient.getPresignedObjectUrl( * GetPresignedObjectUrlArgs.builder() - * .method(Method.GET) + * .method(Http.Method.GET) * .bucket("my-bucketname") * .object("my-objectname") * .expiry(2, TimeUnit.HOURS) @@ -641,20 +640,21 @@ public void removeObject(RemoveObjectArgs args) * objects.add(new DeleteObject("my-objectname1")); * objects.add(new DeleteObject("my-objectname2")); * objects.add(new DeleteObject("my-objectname3")); - * Iterable> results = + * Iterable> results = * minioClient.removeObjects( * RemoveObjectsArgs.builder().bucket("my-bucketname").objects(objects).build()); - * for (Result result : results) { - * DeleteError error = result.get(); + * for (Result result : results) { + * DeleteResult.Error error = result.get(); * System.out.println( * "Error in deleting object " + error.objectName() + "; " + error.message()); * } * } * * @param args {@link RemoveObjectsArgs} object. - * @return {@code Iterable>} - Lazy iterator contains object removal status. + * @return {@code Iterable>} - Lazy iterator contains object removal + * status. */ - public Iterable> removeObjects(RemoveObjectsArgs args) { + public Iterable> removeObjects(RemoveObjectsArgs args) { return asyncClient.removeObjects(args); } @@ -755,13 +755,13 @@ public Iterable> listObjects(ListObjectsArgs args) { * Lists bucket information of all buckets. * *
Example:{@code
-   * List bucketList = minioClient.listBuckets();
+   * List bucketList = minioClient.listBuckets();
    * for (Bucket bucket : bucketList) {
    *   System.out.println(bucket.creationDate() + ", " + bucket.name());
    * }
    * }
* - * @return {@code List} - List of bucket information. + * @return {@code List} - List of bucket information. * @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. @@ -772,7 +772,7 @@ public Iterable> listObjects(ListObjectsArgs args) { * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. * @throws XmlParserException thrown to indicate XML parsing error. */ - public List listBuckets() + public List listBuckets() throws ErrorResponseException, InsufficientDataException, InternalException, InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException, ServerException, XmlParserException { @@ -790,16 +790,17 @@ public List listBuckets() * Lists bucket information of all buckets. * *
Example:{@code
-   * Iterable> results = minioClient.listBuckets(ListBucketsArgs.builder().build());
-   * for (Result result : results) {
+   * Iterable> results = minioClient.listBuckets(ListBucketsArgs.builder().build());
+   * for (Result result : results) {
    *   Bucket bucket = result.get();
    *   System.out.println(String.format("Bucket: %s, Region: %s, CreationDate: %s", bucket.name(), bucket.bucketRegion(), bucket.creationDate()));
    * }
    * }
* - * @return {@link Iterable}<{@link List}<{@link Bucket}>> object. + * @return {@link Iterable}<{@link List}<{@link ListAllMyBucketsResult.Bucket}>> + * object. */ - public Iterable> listBuckets(ListBucketsArgs args) { + public Iterable> listBuckets(ListBucketsArgs args) { return asyncClient.listBuckets(args); } @@ -2624,7 +2625,9 @@ public void setTimeout(long connectTimeout, long writeTimeout, long readTimeout) * @throws KeyManagementException thrown to indicate key management error. * @throws NoSuchAlgorithmException thrown to indicate missing of SSL library. */ - @SuppressFBWarnings(value = "SIC", justification = "Should not be used in production anyways.") + @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( + value = "SIC", + justification = "Should not be used in production anyways.") public void ignoreCertCheck() throws KeyManagementException, NoSuchAlgorithmException { asyncClient.ignoreCertCheck(); } @@ -2660,26 +2663,6 @@ public void traceOff() throws IOException { asyncClient.traceOff(); } - /** - * Enables accelerate endpoint for Amazon S3 endpoint. - * - * @deprecated This method is no longer supported. - */ - @Deprecated - public void enableAccelerateEndpoint() { - asyncClient.enableAccelerateEndpoint(); - } - - /** - * Disables accelerate endpoint for Amazon S3 endpoint. - * - * @deprecated This method is no longer supported. - */ - @Deprecated - public void disableAccelerateEndpoint() { - asyncClient.disableAccelerateEndpoint(); - } - /** Enables dual-stack endpoint for Amazon S3 endpoint. */ public void enableDualStackEndpoint() { asyncClient.enableDualStackEndpoint(); diff --git a/api/src/main/java/io/minio/MinioProperties.java b/api/src/main/java/io/minio/MinioProperties.java deleted file mode 100644 index 95673050a..000000000 --- a/api/src/main/java/io/minio/MinioProperties.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2015 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.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.Enumeration; -import java.util.concurrent.atomic.AtomicReference; -import java.util.jar.Manifest; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** Identifies and stores version information of minio-java package at run time. */ -@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "MS_EXPOSE_REP") -public enum MinioProperties { - INSTANCE; - - private static final Logger LOGGER = Logger.getLogger(MinioProperties.class.getName()); - - private final AtomicReference version = new AtomicReference<>(null); - - public String getVersion() { - String result = version.get(); - if (result != null) { - return result; - } - setVersion(); - return version.get(); - } - - private synchronized void setVersion() { - if (version.get() != null) { - return; - } - version.set("dev"); - ClassLoader classLoader = getClass().getClassLoader(); - if (classLoader == null) { - return; - } - - try { - Enumeration resources = classLoader.getResources("META-INF/MANIFEST.MF"); - while (resources.hasMoreElements()) { - try (InputStream is = resources.nextElement().openStream()) { - Manifest manifest = new Manifest(is); - if ("minio".equals(manifest.getMainAttributes().getValue("Implementation-Title"))) { - version.set(manifest.getMainAttributes().getValue("Implementation-Version")); - return; - } - } - } - } catch (IOException e) { - LOGGER.log(Level.SEVERE, "IOException occurred", e); - version.set("unknown"); - } - } - - public String getDefaultUserAgent() { - return "MinIO (" - + System.getProperty("os.name") - + "; " - + System.getProperty("os.arch") - + ") minio-java/" - + getVersion(); - } -} diff --git a/api/src/main/java/io/minio/MultiOutputStream.java b/api/src/main/java/io/minio/MultiOutputStream.java new file mode 100644 index 000000000..495957d55 --- /dev/null +++ b/api/src/main/java/io/minio/MultiOutputStream.java @@ -0,0 +1,148 @@ +/* + * 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.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class MultiOutputStream extends OutputStream implements AutoCloseable { + private final List outputStreams; + private final ExecutorService executorService; + private final boolean useParallel; + + public MultiOutputStream(List outputStreams, boolean useParallel) { + this.outputStreams = outputStreams; + this.useParallel = useParallel && outputStreams.size() > 2; + this.executorService = + this.useParallel ? Executors.newFixedThreadPool(outputStreams.size()) : null; + } + + private void handleExceptions(List exceptions) throws IOException { + if (exceptions.isEmpty()) return; + if (exceptions.size() == 1) throw exceptions.get(0); + IOException combinedException = new IOException("Multiple IOExceptions occurred"); + for (IOException e : exceptions) combinedException.addSuppressed(e); + throw combinedException; + } + + private void writeToStreamsParallel(byte[] buffer, int off, int len) throws IOException { + List exceptions = new CopyOnWriteArrayList<>(); + List> futures = new ArrayList<>(); + + for (OutputStream outputStream : outputStreams) { + futures.add( + executorService.submit( + () -> { + try { + outputStream.write(buffer, off, len); + outputStream.flush(); + } catch (IOException e) { + exceptions.add(e); + } + })); + } + + for (Future future : futures) { + try { + future.get(5, TimeUnit.SECONDS); // Timeout to prevent indefinite blocking + } catch (TimeoutException e) { + exceptions.add(new IOException("Timeout occurred while writing in parallel", e)); + } catch (ExecutionException e) { + if (e.getCause() instanceof IOException) { + exceptions.add((IOException) e.getCause()); + } else { + exceptions.add(new IOException("Unexpected error in parallel execution", e.getCause())); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + exceptions.add(new IOException("Thread interrupted during parallel execution", e)); + } + } + + handleExceptions(exceptions); + } + + private void writeToStreamsSequential(byte[] buffer, int off, int len) throws IOException { + List exceptions = new ArrayList<>(); + for (OutputStream outputStream : outputStreams) { + try { + outputStream.write(buffer, off, len); + outputStream.flush(); + } catch (IOException e) { + exceptions.add(e); + } + } + handleExceptions(exceptions); + } + + private void writeToStreams(byte[] buffer, int off, int len) throws IOException { + if (useParallel) { + writeToStreamsParallel(buffer, off, len); + } else { + writeToStreamsSequential(buffer, off, len); + } + } + + @Override + public void write(int b) throws IOException { + writeToStreams(new byte[] {(byte) b}, 0, 1); + } + + @Override + public void write(byte[] buffer) throws IOException { + writeToStreams(buffer, 0, buffer.length); + } + + @Override + public void write(byte[] buffer, int off, int len) throws IOException { + writeToStreams(buffer, off, len); + } + + @Override + public void close() throws IOException { + List exceptions = new ArrayList<>(); + for (OutputStream outputStream : outputStreams) { + try { + outputStream.close(); + } catch (IOException e) { + exceptions.add(e); + } + } + if (executorService != null) { + executorService.shutdown(); + try { + if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) { + executorService.shutdownNow(); + } + } catch (InterruptedException e) { + executorService.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + handleExceptions(exceptions); + } +} diff --git a/api/src/main/java/io/minio/ObjectArgs.java b/api/src/main/java/io/minio/ObjectArgs.java index 6ec9c4f46..854d85f0b 100644 --- a/api/src/main/java/io/minio/ObjectArgs.java +++ b/api/src/main/java/io/minio/ObjectArgs.java @@ -30,7 +30,7 @@ public String object() { public abstract static class Builder, A extends ObjectArgs> extends BucketArgs.Builder { protected void validateObjectName(String name) { - validateNotEmptyString(name, "object name"); + Utils.validateNotEmptyString(name, "object name"); if (skipValidation) { return; } diff --git a/api/src/main/java/io/minio/ObjectConditionalReadArgs.java b/api/src/main/java/io/minio/ObjectConditionalReadArgs.java index bdbf9cf67..51b098aad 100644 --- a/api/src/main/java/io/minio/ObjectConditionalReadArgs.java +++ b/api/src/main/java/io/minio/ObjectConditionalReadArgs.java @@ -92,9 +92,9 @@ public Multimap getHeaders() { public Multimap genCopyHeaders() { Multimap headers = HashMultimap.create(); - String copySource = S3Escaper.encodePath("/" + bucketName + "/" + objectName); + String copySource = Utils.encodePath("/" + bucketName + "/" + objectName); if (versionId != null) { - copySource += "?versionId=" + S3Escaper.encode(versionId); + copySource += "?versionId=" + Utils.encode(versionId); } headers.put("x-amz-copy-source", copySource); @@ -147,13 +147,13 @@ public B length(Long length) { } public B matchETag(String etag) { - validateNullOrNotEmptyString(etag, "etag"); + Utils.validateNullOrNotEmptyString(etag, "etag"); operations.add(args -> args.matchETag = etag); return (B) this; } public B notMatchETag(String etag) { - validateNullOrNotEmptyString(etag, "etag"); + Utils.validateNullOrNotEmptyString(etag, "etag"); operations.add(args -> args.notMatchETag = etag); return (B) this; } diff --git a/api/src/main/java/io/minio/ObjectReadArgs.java b/api/src/main/java/io/minio/ObjectReadArgs.java index 2c36048f1..0bfab6ab3 100644 --- a/api/src/main/java/io/minio/ObjectReadArgs.java +++ b/api/src/main/java/io/minio/ObjectReadArgs.java @@ -21,9 +21,9 @@ /** Base argument class for reading object. */ public abstract class ObjectReadArgs extends ObjectVersionArgs { - protected ServerSideEncryptionCustomerKey ssec; + protected ServerSideEncryption.CustomerKey ssec; - public ServerSideEncryptionCustomerKey ssec() { + public ServerSideEncryption.CustomerKey ssec() { return ssec; } @@ -35,7 +35,7 @@ protected void validateSsec(HttpUrl url) { public abstract static class Builder, A extends ObjectReadArgs> extends ObjectVersionArgs.Builder { @SuppressWarnings("unchecked") // Its safe to type cast to B as B is inherited by this class - public B ssec(ServerSideEncryptionCustomerKey ssec) { + public B ssec(ServerSideEncryption.CustomerKey ssec) { operations.add(args -> args.ssec = ssec); return (B) this; } diff --git a/api/src/main/java/io/minio/ObjectWriteArgs.java b/api/src/main/java/io/minio/ObjectWriteArgs.java index b7d76613b..0f06bf743 100644 --- a/api/src/main/java/io/minio/ObjectWriteArgs.java +++ b/api/src/main/java/io/minio/ObjectWriteArgs.java @@ -82,7 +82,7 @@ public Multimap genHeaders() { String tagging = tags.get().entrySet().stream() - .map(e -> S3Escaper.encode(e.getKey()) + "=" + S3Escaper.encode(e.getValue())) + .map(e -> Utils.encode(e.getKey()) + "=" + Utils.encode(e.getValue())) .collect(Collectors.joining("&")); if (!tagging.isEmpty()) { headers.put("x-amz-tagging", tagging); @@ -92,7 +92,7 @@ public Multimap genHeaders() { headers.put("x-amz-object-lock-mode", retention.mode().name()); headers.put( "x-amz-object-lock-retain-until-date", - retention.retainUntilDate().format(Time.RESPONSE_DATE_FORMAT)); + retention.retainUntilDate().format(Time.ISO8601UTC_FORMAT)); } if (legalHold) { @@ -111,13 +111,13 @@ protected void validateSse(HttpUrl url) { public abstract static class Builder, A extends ObjectWriteArgs> extends ObjectArgs.Builder { public B headers(Map headers) { - final Multimap headersCopy = toMultimap(headers); + final Multimap headersCopy = Utils.newMultimap(headers); operations.add(args -> args.headers = headersCopy); return (B) this; } public B headers(Multimap headers) { - final Multimap headersCopy = copyMultimap(headers); + final Multimap headersCopy = Utils.newMultimap(headers); operations.add(args -> args.headers = headersCopy); return (B) this; } diff --git a/api/src/main/java/io/minio/PartReader.java b/api/src/main/java/io/minio/PartReader.java index 9209c4536..e6dbd9564 100644 --- a/api/src/main/java/io/minio/PartReader.java +++ b/api/src/main/java/io/minio/PartReader.java @@ -17,14 +17,17 @@ package io.minio; -import com.google.common.io.BaseEncoding; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; -import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.Locale; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import javax.annotation.Nonnull; /** PartReader reads part data from file or input stream sequentially and returns PartSource. */ @@ -42,12 +45,14 @@ class PartReader { private int partNumber; private long totalDataRead; + private Map hashers; private ByteBufferStream[] buffers; private byte[] oneByte = null; boolean eof; - private PartReader(long objectSize, long partSize, int partCount) { + private PartReader(long objectSize, long partSize, int partCount, Checksum.Algorithm[] algorithms) + throws NoSuchAlgorithmException { this.objectSize = objectSize; this.partSize = partSize; this.partCount = partCount; @@ -57,27 +62,69 @@ private PartReader(long objectSize, long partSize, int partCount) { if (bufferCount == 0) bufferCount++; this.buffers = new ByteBufferStream[(int) bufferCount]; + + if (algorithms != null) { + for (Checksum.Algorithm algorithm : algorithms) { + if (algorithm != null) { + if (this.hashers == null) this.hashers = new HashMap<>(); + this.hashers.put(algorithm, algorithm.hasher()); + } + } + } } - public PartReader(@Nonnull RandomAccessFile file, long objectSize, long partSize, int partCount) { - this(objectSize, partSize, partCount); + public PartReader( + @Nonnull RandomAccessFile file, + long objectSize, + long partSize, + int partCount, + Checksum.Algorithm... algorithms) + throws NoSuchAlgorithmException { + this(objectSize, partSize, partCount, algorithms); this.file = Objects.requireNonNull(file, "file must not be null"); if (this.objectSize < 0) throw new IllegalArgumentException("object size must be provided"); } - public PartReader(@Nonnull InputStream stream, long objectSize, long partSize, int partCount) { - this(objectSize, partSize, partCount); + public PartReader( + @Nonnull InputStream stream, + long objectSize, + long partSize, + int partCount, + Checksum.Algorithm... algorithms) + throws NoSuchAlgorithmException { + this(objectSize, partSize, partCount, algorithms); this.stream = Objects.requireNonNull(stream, "stream must not be null"); for (int i = 0; i < this.buffers.length; i++) this.buffers[i] = new ByteBufferStream(); } - private long readStreamChunk(ByteBufferStream buffer, long size, MessageDigest sha256) - throws IOException { + private void updateHashers(byte[] data, int off, int len) { + if (this.hashers == null || this.hashers.size() == 0) return; + + Set> entries = this.hashers.entrySet(); + if (entries.size() != 1) { + ExecutorService executor = Executors.newFixedThreadPool(this.hashers.size()); + for (Map.Entry entry : entries) { + if (executor.submit(() -> entry.getValue().update(data, off, len)) == null) { + throw new RuntimeException("this should not happen"); + } + } + executor.shutdown(); + try { + executor.awaitTermination(5, TimeUnit.MINUTES); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } else { + entries.iterator().next().getValue().update(data, off, len); + } + } + + private long readStreamChunk(ByteBufferStream buffer, long size) throws IOException { long totalBytesRead = 0; if (this.oneByte != null) { buffer.write(this.oneByte); - sha256.update(this.oneByte); + this.updateHashers(this.oneByte, 0, this.oneByte.length); totalBytesRead++; this.oneByte = null; } @@ -92,14 +139,14 @@ private long readStreamChunk(ByteBufferStream buffer, long size, MessageDigest s throw new IOException("unexpected EOF"); } buffer.write(this.buf16k, 0, bytesRead); - sha256.update(this.buf16k, 0, bytesRead); + this.updateHashers(this.buf16k, 0, bytesRead); totalBytesRead += bytesRead; } return totalBytesRead; } - private long readStream(long size, MessageDigest sha256) throws IOException { + private long readStream(long size) throws IOException { long count = size / CHUNK_SIZE; long lastChunkSize = size - (count * CHUNK_SIZE); if (lastChunkSize > 0) { @@ -112,7 +159,7 @@ private long readStream(long size, MessageDigest sha256) throws IOException { for (int i = 0; i < buffers.length; i++) buffers[i].reset(); for (long i = 1; i <= count && !this.eof; i++) { long chunkSize = (i != count) ? CHUNK_SIZE : lastChunkSize; - long bytesRead = this.readStreamChunk(buffers[(int) (i - 1)], chunkSize, sha256); + long bytesRead = this.readStreamChunk(buffers[(int) (i - 1)], chunkSize); totalBytesRead += bytesRead; } @@ -124,7 +171,7 @@ private long readStream(long size, MessageDigest sha256) throws IOException { return totalBytesRead; } - private long readFile(long size, MessageDigest sha256) throws IOException { + private long readFile(long size) throws IOException { long position = this.file.getFilePointer(); long totalBytesRead = 0; @@ -133,7 +180,7 @@ private long readFile(long size, MessageDigest sha256) throws IOException { if (bytesToRead > this.buf16k.length) bytesToRead = this.buf16k.length; int bytesRead = this.file.read(this.buf16k, 0, (int) bytesToRead); if (bytesRead < 0) throw new IOException("unexpected EOF"); - sha256.update(this.buf16k, 0, bytesRead); + this.updateHashers(this.buf16k, 0, bytesRead); totalBytesRead += bytesRead; } @@ -141,8 +188,8 @@ private long readFile(long size, MessageDigest sha256) throws IOException { return totalBytesRead; } - private long read(long size, MessageDigest sha256) throws IOException { - return (this.file != null) ? readFile(size, sha256) : readStream(size, sha256); + private long read(long size) throws IOException { + return (this.file != null) ? readFile(size) : readStream(size); } public PartSource getPart() throws NoSuchAlgorithmException, IOException { @@ -150,21 +197,25 @@ public PartSource getPart() throws NoSuchAlgorithmException, IOException { this.partNumber++; - MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); - long partSize = this.partSize; if (this.partNumber == this.partCount) partSize = this.objectSize - this.totalDataRead; - long bytesRead = this.read(partSize, sha256); + long bytesRead = this.read(partSize); this.totalDataRead += bytesRead; if (this.objectSize < 0 && this.eof) this.partCount = this.partNumber; - String sha256Hash = BaseEncoding.base16().encode(sha256.digest()).toLowerCase(Locale.US); + Map checksums = null; + if (this.hashers != null) { + checksums = new HashMap<>(); + for (Map.Entry entry : this.hashers.entrySet()) { + checksums.put(entry.getKey(), entry.getValue().sum()); + } + } if (this.file != null) { - return new PartSource(this.partNumber, this.file, bytesRead, null, sha256Hash); + return new PartSource(this.partNumber, this.file, bytesRead, checksums); } - return new PartSource(this.partNumber, this.buffers, bytesRead, null, sha256Hash); + return new PartSource(this.partNumber, this.buffers, bytesRead, checksums); } public int partCount() { diff --git a/api/src/main/java/io/minio/PartSource.java b/api/src/main/java/io/minio/PartSource.java index b18581eb5..304edbcf3 100644 --- a/api/src/main/java/io/minio/PartSource.java +++ b/api/src/main/java/io/minio/PartSource.java @@ -25,36 +25,34 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Objects; import javax.annotation.Nonnull; -import okio.Okio; -import okio.Source; /** Part source information. */ class PartSource { private int partNumber; private long size; - private String md5Hash; - private String sha256Hash; + private Map checksums; private RandomAccessFile file; private long position; private ByteBufferStream[] buffers; - private InputStream inputStream; - - private PartSource(int partNumber, long size, String md5Hash, String sha256Hash) { + private PartSource(int partNumber, long size, Map checksums) { this.partNumber = partNumber; this.size = size; - this.md5Hash = md5Hash; - this.sha256Hash = sha256Hash; + this.checksums = checksums; } public PartSource( - int partNumber, @Nonnull RandomAccessFile file, long size, String md5Hash, String sha256Hash) + int partNumber, + @Nonnull RandomAccessFile file, + long size, + Map checksums) throws IOException { - this(partNumber, size, md5Hash, sha256Hash); + this(partNumber, size, checksums); this.file = Objects.requireNonNull(file, "file must not be null"); this.position = this.file.getFilePointer(); } @@ -63,17 +61,11 @@ public PartSource( int partNumber, @Nonnull ByteBufferStream[] buffers, long size, - String md5Hash, - String sha256Hash) { - this(partNumber, size, md5Hash, sha256Hash); + Map checksums) { + this(partNumber, size, checksums); 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; } @@ -82,26 +74,18 @@ public long size() { return this.size; } - public String md5Hash() { - return this.md5Hash; + public Map checksums() { + return checksums; } - public String sha256Hash() { - return this.sha256Hash; - } - - public Source source() throws IOException { + public InputStream inputStream() throws IOException { if (this.file != null) { this.file.seek(this.position); - return Okio.source(Channels.newInputStream(this.file.getChannel())); - } - - if (this.inputStream != null) { - return Okio.source(this.inputStream); + return Channels.newInputStream(this.file.getChannel()); } InputStream stream = buffers[0].inputStream(); - if (buffers.length == 1) return Okio.source(stream); + if (buffers.length == 1) return stream; List streams = new ArrayList<>(); streams.add(stream); @@ -109,7 +93,7 @@ public Source source() throws IOException { if (buffers[i].size() == 0) break; streams.add(buffers[i].inputStream()); } - if (streams.size() == 1) return Okio.source(stream); - return Okio.source(new SequenceInputStream(Collections.enumeration(streams))); + if (streams.size() == 1) return stream; + return new SequenceInputStream(Collections.enumeration(streams)); } } diff --git a/api/src/main/java/io/minio/PostPolicy.java b/api/src/main/java/io/minio/PostPolicy.java index 204d06a30..3e0e2cb53 100644 --- a/api/src/main/java/io/minio/PostPolicy.java +++ b/api/src/main/java/io/minio/PostPolicy.java @@ -187,7 +187,7 @@ public Map formData(@Nonnull Credentials creds, @Nonnull String } Map policyMap = new HashMap<>(); - policyMap.put("expiration", expiration.format(Time.EXPIRATION_DATE_FORMAT)); + policyMap.put("expiration", expiration.format(Time.ISO8601UTC_FORMAT)); List> conditionList = new LinkedList<>(); conditionList.add(Arrays.asList(new Object[] {"eq", "$bucket", bucketName})); for (Map.Entry> condition : conditions.entrySet()) { diff --git a/api/src/main/java/io/minio/PromptObjectArgs.java b/api/src/main/java/io/minio/PromptObjectArgs.java index f092f4c65..cc115eb2b 100644 --- a/api/src/main/java/io/minio/PromptObjectArgs.java +++ b/api/src/main/java/io/minio/PromptObjectArgs.java @@ -51,25 +51,25 @@ public static final class Builder extends ObjectArgs.Builder args.prompt = prompt); return this; } public Builder lambdaArn(String lambdaArn) { - validateNotEmptyString(lambdaArn, "lambda ARN"); + Utils.validateNotEmptyString(lambdaArn, "lambda ARN"); operations.add(args -> args.lambdaArn = lambdaArn); return this; } public Builder promptArgs(Map promptArgs) { - validateNotNull(promptArgs, "prompt argument"); + Utils.validateNotNull(promptArgs, "prompt argument"); operations.add(args -> args.promptArgs = promptArgs); return this; } diff --git a/api/src/main/java/io/minio/PutObjectAPIArgs.java b/api/src/main/java/io/minio/PutObjectAPIArgs.java new file mode 100644 index 000000000..e90692ccd --- /dev/null +++ b/api/src/main/java/io/minio/PutObjectAPIArgs.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; + +import com.google.common.collect.Multimap; +import java.io.RandomAccessFile; +import java.util.Objects; + +/** Argument class of {@link MinioAsyncClient#putObjectAPI} and {@link MinioClient#putObjectAPI}. */ +public class PutObjectAPIArgs extends ObjectArgs { + private RandomAccessFile file; + private ByteBuffer buffer; + private byte[] data; + private Long length; + private Multimap headers; + + public RandomAccessFile file() { + return file; + } + + public ByteBuffer buffer() { + return buffer; + } + + public byte[] data() { + return data; + } + + public Long length() { + return length; + } + + public Multimap headers() { + return headers; + } + + public static Builder builder() { + return new Builder(); + } + + /** Argument builder of {@link PutObjectAPIArgs}. */ + public static final class Builder extends ObjectArgs.Builder { + @Override + protected void validate(PutObjectAPIArgs args) { + super.validate(args); + if (!((args.file != null) != (args.buffer != null) != (args.data != null) + && !(args.file != null && args.buffer != null && args.data != null))) { + throw new IllegalArgumentException("only one of file, buffer or data must be provided"); + } + } + + public Builder file(RandomAccessFile file, long length) { + Utils.validateNotNull(file, "file"); + if (length < 0) throw new IllegalArgumentException("valid length must be provided"); + operations.add(args -> args.file = file); + operations.add(args -> args.length = length); + return this; + } + + public Builder buffer(ByteBuffer buffer) { + Utils.validateNotNull(buffer, "buffer"); + operations.add(args -> args.buffer = buffer); + return this; + } + + public Builder data(byte[] data, int length) { + Utils.validateNotNull(data, "data"); + if (length < 0) throw new IllegalArgumentException("valid length must be provided"); + operations.add(args -> args.data = data); + operations.add(args -> args.length = (long) length); + return this; + } + + public Builder headers(Multimap headers) { + operations.add(args -> args.headers = headers); + return this; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PutObjectAPIArgs)) return false; + if (!super.equals(o)) return false; + PutObjectAPIArgs that = (PutObjectAPIArgs) o; + return Objects.equals(file, that.file) + && Objects.equals(buffer, that.buffer) + && Objects.equals(data, that.data) + && Objects.equals(length, that.length) + && Objects.equals(headers, that.headers); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), file, buffer, data, length, headers); + } +} diff --git a/api/src/main/java/io/minio/PutObjectArgs.java b/api/src/main/java/io/minio/PutObjectArgs.java index bdc2f5869..5bc806ced 100644 --- a/api/src/main/java/io/minio/PutObjectArgs.java +++ b/api/src/main/java/io/minio/PutObjectArgs.java @@ -47,7 +47,7 @@ public static final class Builder extends PutObjectBaseArgs.BuilderA valid part size is between 5MiB to 5GiB (both limits inclusive). */ public Builder stream(InputStream stream, long objectSize, long partSize) { - validateNotNull(stream, "stream"); + Utils.validateNotNull(stream, "stream"); long[] partinfo = getPartInfo(objectSize, partSize); long pSize = partinfo[0]; diff --git a/api/src/main/java/io/minio/PutObjectBaseArgs.java b/api/src/main/java/io/minio/PutObjectBaseArgs.java index aaec5e1bb..e9fd78532 100644 --- a/api/src/main/java/io/minio/PutObjectBaseArgs.java +++ b/api/src/main/java/io/minio/PutObjectBaseArgs.java @@ -62,7 +62,7 @@ public boolean preloadData() { public abstract static class Builder, A extends PutObjectBaseArgs> extends ObjectWriteArgs.Builder { protected void validateContentType(String contentType) { - validateNotEmptyString(contentType, "content type"); + Utils.validateNotEmptyString(contentType, "content type"); if (MediaType.parse(contentType) == null) { throw new IllegalArgumentException( "invalid content type '" + contentType + "' as per RFC 2045"); diff --git a/api/src/main/java/io/minio/PutObjectFanOutArgs.java b/api/src/main/java/io/minio/PutObjectFanOutArgs.java index 64ad1cb60..c8810dbcd 100644 --- a/api/src/main/java/io/minio/PutObjectFanOutArgs.java +++ b/api/src/main/java/io/minio/PutObjectFanOutArgs.java @@ -87,12 +87,12 @@ public static final class Builder extends BucketArgs.Builder { @Override protected void validate(PutObjectFanOutEntry args) { - validateNotEmptyString(args.key, "key"); + Utils.validateNotEmptyString(args.key, "key"); } public Builder key(String key) { - validateNotEmptyString(key, "key"); + Utils.validateNotEmptyString(key, "key"); operations.add(args -> args.key = key); return this; } diff --git a/api/src/main/java/io/minio/PutObjectFanOutResponse.java b/api/src/main/java/io/minio/PutObjectFanOutResponse.java index e1f28be06..6b7e9efca 100644 --- a/api/src/main/java/io/minio/PutObjectFanOutResponse.java +++ b/api/src/main/java/io/minio/PutObjectFanOutResponse.java @@ -17,6 +17,7 @@ package io.minio; import com.fasterxml.jackson.annotation.JsonProperty; +import java.time.ZonedDateTime; import java.util.List; import okhttp3.Headers; @@ -48,8 +49,7 @@ public static class Result { private String versionId; @JsonProperty("lastModified") - private String lastModified; - // private ResponseDate lastModified; + private Time.S3Time lastModified; @JsonProperty("error") private String error; @@ -68,8 +68,8 @@ public String versionId() { return versionId; } - public String lastModified() { - return lastModified; + public ZonedDateTime lastModified() { + return lastModified == null ? null : lastModified.toZonedDateTime(); } public String error() { diff --git a/api/src/main/java/io/minio/Reader.java b/api/src/main/java/io/minio/Reader.java new file mode 100644 index 000000000..1378a4fe1 --- /dev/null +++ b/api/src/main/java/io/minio/Reader.java @@ -0,0 +1,116 @@ +/* + * 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.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import javax.annotation.Nonnull; + +/** Reader reads part data from file or input stream sequentially and returns PartSource. */ +class Reader { + private byte[] buf16k = new byte[16384]; // 16KiB buffer for optimization. + + private InputStream stream; + private Long objectSize; + private long partSize; + + private long bytesRead = 0; + private Byte oneByte = null; + boolean endOfStream = false; + + public Reader(@Nonnull InputStream stream, Long objectSize, long partSize) { + this.stream = Utils.validateNotNull(stream, "stream"); + if (partSize < 0) throw new IllegalArgumentException("part size must be provided"); + this.objectSize = objectSize; + this.partSize = partSize; + } + + private long copyStream(OutputStream os, long length) throws IOException { + long bytesCopied = 0; + int bytesRead = 0; + while (bytesCopied < length + && (bytesRead = stream.read(buf16k, 0, (int) Math.min(buf16k.length, length - bytesCopied))) + != -1) { + os.write(buf16k, 0, bytesRead); + bytesCopied += bytesRead; + } + return bytesCopied; + } + + private void readUnknownSizedStream(OutputStream os) throws IOException { + long partSize = this.partSize; + + if (oneByte != null) { + os.write(oneByte); + oneByte = null; + partSize--; + } + + if (copyStream(os, partSize) == partSize) { + int b = stream.read(); + if (b != -1) { + oneByte = (byte) b; + } else { + endOfStream = true; + } + } else { + endOfStream = true; + } + } + + private void readSizedStream(OutputStream os) throws IOException { + long partSize = (long) Math.min(this.partSize, objectSize - bytesRead); + long bytesCopied = copyStream(os, partSize); + bytesRead += bytesCopied; + if (bytesCopied != partSize) { + endOfStream = true; + throw new IOException( + "Unexpected end of stream; expected=" + partSize + ", got=" + bytesCopied); + } + endOfStream = bytesRead == objectSize; + } + + public void read(OutputStream os) throws IOException { + if (objectSize == null) { + readUnknownSizedStream(os); + } else { + readSizedStream(os); + } + } + + public boolean endOfStream() { + return endOfStream; + } + + // public void check(OutputStream os, InputStream is, Long objectSize, long partSize) { + // Integer partCount = null; + // if (objectSize != null) partCount = (int) Math.max(1, (objectSize + partSize - 1) / + // partSize); + // for (int partNumber = 1; partCount == null || partNumber <= partCount; partNumber++) { + // read(os); + // if (partCount == null && endOfStream()) partCount = partNumber; + // + // if (partCount != null && partNumber == partCount) { + // doSinglePutObbject(); + // } else { + // uploadPart(); + // } + // } + // } +} diff --git a/api/src/main/java/io/minio/RemoveObjectsArgs.java b/api/src/main/java/io/minio/RemoveObjectsArgs.java index a3b678e65..b03e6b94f 100644 --- a/api/src/main/java/io/minio/RemoveObjectsArgs.java +++ b/api/src/main/java/io/minio/RemoveObjectsArgs.java @@ -16,7 +16,7 @@ package io.minio; -import io.minio.messages.DeleteObject; +import io.minio.messages.DeleteRequest; import java.util.LinkedList; import java.util.Objects; @@ -25,13 +25,13 @@ */ public class RemoveObjectsArgs extends BucketArgs { private boolean bypassGovernanceMode; - private Iterable objects = new LinkedList<>(); + private Iterable objects = new LinkedList<>(); public boolean bypassGovernanceMode() { return bypassGovernanceMode; } - public Iterable objects() { + public Iterable objects() { return objects; } @@ -46,8 +46,8 @@ public Builder bypassGovernanceMode(boolean flag) { return this; } - public Builder objects(Iterable objects) { - validateNotNull(objects, "objects"); + public Builder objects(Iterable objects) { + Utils.validateNotNull(objects, "objects"); operations.add(args -> args.objects = objects); return this; } diff --git a/api/src/main/java/io/minio/RestoreObjectArgs.java b/api/src/main/java/io/minio/RestoreObjectArgs.java index 14467c6e1..ae606b7d9 100644 --- a/api/src/main/java/io/minio/RestoreObjectArgs.java +++ b/api/src/main/java/io/minio/RestoreObjectArgs.java @@ -34,7 +34,7 @@ public static Builder builder() { /** Argument builder of {@link RestoreObjectArgs}. */ public static final class Builder extends ObjectVersionArgs.Builder { private void validateRequest(RestoreRequest request) { - validateNotNull(request, "request"); + Utils.validateNotNull(request, "request"); } public Builder request(RestoreRequest request) { diff --git a/api/src/main/java/io/minio/S3Base.java b/api/src/main/java/io/minio/S3Base.java deleted file mode 100644 index 324637ccb..000000000 --- a/api/src/main/java/io/minio/S3Base.java +++ /dev/null @@ -1,3909 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, - * (C) 2021 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.JsonParseException; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.MapperFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.json.JsonMapper; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Multimap; -import com.google.common.collect.Multimaps; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import io.minio.credentials.Credentials; -import io.minio.credentials.Provider; -import io.minio.errors.ErrorResponseException; -import io.minio.errors.InsufficientDataException; -import io.minio.errors.InternalException; -import io.minio.errors.InvalidResponseException; -import io.minio.errors.ServerException; -import io.minio.errors.XmlParserException; -import io.minio.http.HttpUtils; -import io.minio.http.Method; -import io.minio.messages.CompleteMultipartUpload; -import io.minio.messages.CompleteMultipartUploadResult; -import io.minio.messages.CopyPartResult; -import io.minio.messages.DeleteError; -import io.minio.messages.DeleteMarker; -import io.minio.messages.DeleteObject; -import io.minio.messages.DeleteRequest; -import io.minio.messages.DeleteResult; -import io.minio.messages.ErrorResponse; -import io.minio.messages.InitiateMultipartUploadResult; -import io.minio.messages.Item; -import io.minio.messages.ListAllMyBucketsResult; -import io.minio.messages.ListBucketResultV1; -import io.minio.messages.ListBucketResultV2; -import io.minio.messages.ListMultipartUploadsResult; -import io.minio.messages.ListObjectsResult; -import io.minio.messages.ListPartsResult; -import io.minio.messages.ListVersionsResult; -import io.minio.messages.LocationConstraint; -import io.minio.messages.NotificationRecords; -import io.minio.messages.Part; -import io.minio.messages.Prefix; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.RandomAccessFile; -import java.nio.charset.StandardCharsets; -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; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -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; -import java.util.concurrent.CompletionException; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.logging.Logger; -import java.util.stream.Collectors; -import javax.annotation.Nonnull; -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.Headers; -import okhttp3.HttpUrl; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; -import okhttp3.ResponseBody; - -/** Core S3 API client. */ -public abstract class S3Base implements AutoCloseable { - static { - try { - RequestBody.create(new byte[] {}, null); - } catch (NoSuchMethodError ex) { - throw new RuntimeException("Unsupported OkHttp library found. Must use okhttp >= 4.11.0", ex); - } - } - - protected static final String NO_SUCH_BUCKET_MESSAGE = "Bucket does not exist"; - protected static final String NO_SUCH_BUCKET = "NoSuchBucket"; - protected static final String NO_SUCH_BUCKET_POLICY = "NoSuchBucketPolicy"; - protected static final String NO_SUCH_OBJECT_LOCK_CONFIGURATION = "NoSuchObjectLockConfiguration"; - protected static final String SERVER_SIDE_ENCRYPTION_CONFIGURATION_NOT_FOUND_ERROR = - "ServerSideEncryptionConfigurationNotFoundError"; - protected static final long DEFAULT_CONNECTION_TIMEOUT = TimeUnit.MINUTES.toMillis(5); - // maximum allowed bucket policy size is 20KiB - 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, "acl", "attributes"); - private PrintWriter traceStream; - private String userAgent = MinioProperties.INSTANCE.getDefaultUserAgent(); - - protected HttpUrl baseUrl; - protected String awsS3Prefix; - protected String awsDomainSuffix; - protected boolean awsDualstack; - protected boolean useVirtualStyle; - protected String region; - protected Provider provider; - protected OkHttpClient httpClient; - protected boolean closeHttpClient; - - /** @deprecated This method is no longer supported. */ - @Deprecated - protected S3Base( - HttpUrl baseUrl, - String awsS3Prefix, - String awsDomainSuffix, - boolean awsDualstack, - boolean useVirtualStyle, - String region, - Provider provider, - OkHttpClient httpClient) { - this( - baseUrl, - awsS3Prefix, - awsDomainSuffix, - awsDualstack, - useVirtualStyle, - region, - provider, - httpClient, - false); - } - - protected S3Base( - HttpUrl baseUrl, - String awsS3Prefix, - String awsDomainSuffix, - boolean awsDualstack, - boolean useVirtualStyle, - String region, - Provider provider, - OkHttpClient httpClient, - boolean closeHttpClient) { - this.baseUrl = baseUrl; - this.awsS3Prefix = awsS3Prefix; - this.awsDomainSuffix = awsDomainSuffix; - this.awsDualstack = awsDualstack; - this.useVirtualStyle = useVirtualStyle; - this.region = region; - this.provider = provider; - this.httpClient = httpClient; - this.closeHttpClient = closeHttpClient; - } - - /** @deprecated This method is no longer supported. */ - @Deprecated - protected S3Base( - HttpUrl baseUrl, - String region, - boolean isAwsHost, - boolean isFipsHost, - boolean isAccelerateHost, - boolean isDualStackHost, - boolean useVirtualStyle, - Provider provider, - OkHttpClient httpClient) { - this.baseUrl = baseUrl; - if (isAwsHost) this.awsS3Prefix = "s3."; - if (isFipsHost) this.awsS3Prefix = "s3-fips."; - if (isAccelerateHost) this.awsS3Prefix = "s3-accelerate."; - if (isAwsHost || isFipsHost || isAccelerateHost) { - String host = baseUrl.host(); - if (host.endsWith(".amazonaws.com")) this.awsDomainSuffix = "amazonaws.com"; - if (host.endsWith(".amazonaws.com.cn")) this.awsDomainSuffix = "amazonaws.com.cn"; - } - this.awsDualstack = isDualStackHost; - this.useVirtualStyle = useVirtualStyle; - this.region = region; - this.provider = provider; - this.httpClient = httpClient; - this.closeHttpClient = false; - } - - protected S3Base(S3Base client) { - this.baseUrl = client.baseUrl; - this.awsS3Prefix = client.awsS3Prefix; - this.awsDomainSuffix = client.awsDomainSuffix; - this.awsDualstack = client.awsDualstack; - this.useVirtualStyle = client.useVirtualStyle; - this.region = client.region; - this.provider = client.provider; - this.httpClient = client.httpClient; - this.closeHttpClient = client.closeHttpClient; - } - - /** Check whether argument is valid or not. */ - protected void checkArgs(BaseArgs args) { - if (args == null) throw new IllegalArgumentException("null arguments"); - - if ((this.awsDomainSuffix != null) && (args instanceof BucketArgs)) { - String bucketName = ((BucketArgs) args).bucket(); - if (bucketName.startsWith("xn--") - || bucketName.endsWith("--s3alias") - || bucketName.endsWith("--ol-s3")) { - throw new IllegalArgumentException( - "bucket name '" - + bucketName - + "' must not start with 'xn--' and must not end with '--s3alias' or '--ol-s3'"); - } - } - } - - /** Merge two Multimaps. */ - protected Multimap merge( - Multimap m1, Multimap m2) { - Multimap map = HashMultimap.create(); - if (m1 != null) map.putAll(m1); - if (m2 != null) map.putAll(m2); - return map; - } - - /** Create new HashMultimap by alternating keys and values. */ - protected Multimap newMultimap(String... keysAndValues) { - if (keysAndValues.length % 2 != 0) { - throw new IllegalArgumentException("Expected alternating keys and values"); - } - - Multimap map = HashMultimap.create(); - for (int i = 0; i < keysAndValues.length; i += 2) { - map.put(keysAndValues[i], keysAndValues[i + 1]); - } - - return map; - } - - /** Create new HashMultimap with copy of Map. */ - protected Multimap newMultimap(Map map) { - return (map != null) ? Multimaps.forMap(map) : HashMultimap.create(); - } - - /** Create new HashMultimap with copy of Multimap. */ - protected Multimap newMultimap(Multimap map) { - return (map != null) ? HashMultimap.create(map) : HashMultimap.create(); - } - - /** Throws encapsulated exception wrapped by {@link ExecutionException}. */ - public void throwEncapsulatedException(ExecutionException e) - throws ErrorResponseException, InsufficientDataException, InternalException, - InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException, - ServerException, XmlParserException { - if (e == null) return; - - Throwable ex = e.getCause(); - - if (ex instanceof CompletionException) { - ex = ((CompletionException) ex).getCause(); - } - - if (ex instanceof ExecutionException) { - ex = ((ExecutionException) ex).getCause(); - } - - try { - throw ex; - } catch (IllegalArgumentException - | ErrorResponseException - | InsufficientDataException - | InternalException - | InvalidKeyException - | InvalidResponseException - | IOException - | NoSuchAlgorithmException - | ServerException - | XmlParserException exc) { - throw exc; - } catch (Throwable exc) { - throw new RuntimeException(exc.getCause() == null ? exc : exc.getCause()); - } - } - - private String[] handleRedirectResponse( - Method method, String bucketName, Response response, boolean retry) { - String code = null; - String message = null; - - if (response.code() == 301) { - code = "PermanentRedirect"; - message = "Moved Permanently"; - } else if (response.code() == 307) { - code = "Redirect"; - message = "Temporary redirect"; - } else if (response.code() == 400) { - code = "BadRequest"; - message = "Bad request"; - } - - String region = response.headers().get("x-amz-bucket-region"); - if (message != null && region != null) message += ". Use region " + region; - - if (retry - && region != null - && method.equals(Method.HEAD) - && bucketName != null - && regionCache.get(bucketName) != null) { - code = RETRY_HEAD; - message = null; - } - - return new String[] {code, message}; - } - - private String buildAwsUrl( - HttpUrl.Builder builder, String bucketName, boolean enforcePathStyle, String region) { - String host = this.awsS3Prefix + this.awsDomainSuffix; - if (host.equals("s3-external-1.amazonaws.com") - || host.equals("s3-us-gov-west-1.amazonaws.com") - || host.equals("s3-fips-us-gov-west-1.amazonaws.com")) { - builder.host(host); - return host; - } - - host = this.awsS3Prefix; - if (this.awsS3Prefix.contains("s3-accelerate")) { - if (bucketName.contains(".")) { - throw new IllegalArgumentException( - "bucket name '" + bucketName + "' with '.' is not allowed for accelerate endpoint"); - } - if (enforcePathStyle) host = host.replaceFirst("-accelerate", ""); - } - - if (this.awsDualstack) host += "dualstack."; - if (!this.awsS3Prefix.contains("s3-accelerate")) host += region + "."; - host += this.awsDomainSuffix; - - builder.host(host); - return host; - } - - private String buildListBucketsUrl(HttpUrl.Builder builder, String region) { - if (this.awsDomainSuffix == null) return null; - - String host = this.awsS3Prefix + this.awsDomainSuffix; - if (host.equals("s3-external-1.amazonaws.com") - || host.equals("s3-us-gov-west-1.amazonaws.com") - || host.equals("s3-fips-us-gov-west-1.amazonaws.com")) { - builder.host(host); - return host; - } - - String s3Prefix = this.awsS3Prefix; - String domainSuffix = this.awsDomainSuffix; - if (this.awsS3Prefix.startsWith("s3.") || this.awsS3Prefix.startsWith("s3-")) { - s3Prefix = "s3."; - domainSuffix = "amazonaws.com" + (domainSuffix.endsWith(".cn") ? ".cn" : ""); - } - - host = s3Prefix + region + "." + domainSuffix; - builder.host(host); - return host; - } - - /** Build URL for given parameters. */ - protected HttpUrl buildUrl( - Method method, - String bucketName, - String objectName, - String region, - Multimap queryParamMap) - throws NoSuchAlgorithmException { - if (bucketName == null && objectName != null) { - throw new IllegalArgumentException("null bucket name for object '" + objectName + "'"); - } - - HttpUrl.Builder urlBuilder = this.baseUrl.newBuilder(); - - if (queryParamMap != null) { - for (Map.Entry entry : queryParamMap.entries()) { - urlBuilder.addEncodedQueryParameter( - S3Escaper.encode(entry.getKey()), S3Escaper.encode(entry.getValue())); - } - } - - if (bucketName == null) { - this.buildListBucketsUrl(urlBuilder, region); - return urlBuilder.build(); - } - - boolean enforcePathStyle = ( - // use path style for make bucket to workaround "AuthorizationHeaderMalformed" error from - // s3.amazonaws.com - (method == Method.PUT && objectName == null && queryParamMap == null) - - // use path style for location query - || (queryParamMap != null && queryParamMap.containsKey("location")) - - // use path style where '.' in bucketName causes SSL certificate validation error - || (bucketName.contains(".") && this.baseUrl.isHttps())); - - String host = this.baseUrl.host(); - if (this.awsDomainSuffix != null) { - host = this.buildAwsUrl(urlBuilder, bucketName, enforcePathStyle, region); - } - - if (enforcePathStyle || !this.useVirtualStyle) { - urlBuilder.addEncodedPathSegment(S3Escaper.encode(bucketName)); - } else { - urlBuilder.host(bucketName + "." + host); - } - - if (objectName != null) { - urlBuilder.addEncodedPathSegments(S3Escaper.encodePath(objectName)); - } - - return urlBuilder.build(); - } - - /** Convert Multimap to Headers. */ - protected Headers httpHeaders(Multimap headerMap) { - Headers.Builder builder = new Headers.Builder(); - if (headerMap == null) return builder.build(); - - if (headerMap.containsKey("Content-Encoding")) { - builder.add( - "Content-Encoding", - headerMap.get("Content-Encoding").stream() - .distinct() - .filter(encoding -> !encoding.isEmpty()) - .collect(Collectors.joining(","))); - } - - for (Map.Entry entry : headerMap.entries()) { - if (!entry.getKey().equals("Content-Encoding")) { - builder.addUnsafeNonAscii(entry.getKey(), entry.getValue()); - } - } - - return builder.build(); - } - - /** Create HTTP request for given paramaters. */ - protected Request createRequest( - HttpUrl url, Method method, Headers headers, Object body, int length, Credentials creds) - throws InsufficientDataException, InternalException, IOException, NoSuchAlgorithmException { - Request.Builder requestBuilder = new Request.Builder(); - requestBuilder.url(url); - - if (headers != null) requestBuilder.headers(headers); - requestBuilder.header("Host", HttpUtils.getHostHeader(url)); - // Disable default gzip compression by okhttp library. - 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; - } - - String sha256Hash = null; - if (creds != null) { - sha256Hash = Digest.ZERO_SHA256_HASH; - if (!url.isHttps()) { - if (body != null) { - if (body instanceof PartSource) { - sha256Hash = ((PartSource) body).sha256Hash(); - } else if (body instanceof byte[]) { - sha256Hash = Digest.sha256Hash((byte[]) body, length); - } - } - } else { - // Fix issue #415: No need to compute sha256 if endpoint scheme is HTTPS. - sha256Hash = "UNSIGNED-PAYLOAD"; - if (body != null && body instanceof PartSource) { - sha256Hash = ((PartSource) body).sha256Hash(); - } - } - } - - if (md5Hash != null) requestBuilder.header("Content-MD5", md5Hash); - if (sha256Hash != null) requestBuilder.header("x-amz-content-sha256", sha256Hash); - - if (creds != null && creds.sessionToken() != null) { - requestBuilder.header("X-Amz-Security-Token", creds.sessionToken()); - } - - ZonedDateTime date = ZonedDateTime.now(); - requestBuilder.header("x-amz-date", date.format(Time.AMZ_DATE_FORMAT)); - - RequestBody requestBody = null; - if (body != null) { - String contentType = (headers != null) ? headers.get("Content-Type") : null; - if (contentType != null && MediaType.parse(contentType) == null) { - throw new IllegalArgumentException( - "invalid content type '" + contentType + "' as per RFC 2045"); - } - - if (body instanceof PartSource) { - requestBody = new HttpRequestBody((PartSource) body, contentType); - } else { - requestBody = new HttpRequestBody((byte[]) body, length, contentType); - } - } - - requestBuilder.method(method.toString(), requestBody); - return requestBuilder.build(); - } - - private StringBuilder newTraceBuilder(Request request, String body) { - StringBuilder traceBuilder = new StringBuilder(); - traceBuilder.append("---------START-HTTP---------\n"); - String encodedPath = request.url().encodedPath(); - String encodedQuery = request.url().encodedQuery(); - if (encodedQuery != null) encodedPath += "?" + encodedQuery; - traceBuilder.append(request.method()).append(" ").append(encodedPath).append(" HTTP/1.1\n"); - traceBuilder.append( - request - .headers() - .toString() - .replaceAll("Signature=([0-9a-f]+)", "Signature=*REDACTED*") - .replaceAll("Credential=([^/]+)", "Credential=*REDACTED*")); - if (body != null) traceBuilder.append("\n").append(body); - return traceBuilder; - } - - /** Execute HTTP request asynchronously for given parameters. */ - protected CompletableFuture executeAsync( - Method method, - String bucketName, - String objectName, - String region, - Headers headers, - Multimap queryParamMap, - Object body, - int length) - throws InsufficientDataException, InternalException, InvalidKeyException, IOException, - NoSuchAlgorithmException, XmlParserException { - boolean traceRequestBody = false; - 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); - } else { - bytes = Xml.marshal(body).getBytes(StandardCharsets.UTF_8); - } - - body = bytes; - length = bytes.length; - traceRequestBody = true; - } - - if (body == null && (method == Method.PUT || method == Method.POST)) { - body = HttpUtils.EMPTY_BODY; - } - - 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 (!(body != null && body instanceof RequestBody) && creds != null) { - req = - Signer.signV4S3( - req, - region, - creds.accessKey(), - creds.secretKey(), - req.header("x-amz-content-sha256")); - } - final Request request = req; - - StringBuilder traceBuilder = - newTraceBuilder( - request, traceRequestBody ? new String((byte[]) body, StandardCharsets.UTF_8) : null); - PrintWriter traceStream = this.traceStream; - if (traceStream != null) traceStream.println(traceBuilder.toString()); - traceBuilder.append("\n"); - - OkHttpClient httpClient = this.httpClient; - if (!(body instanceof byte[]) && (method == Method.PUT || method == Method.POST)) { - // Issue #924: disable connection retry for PUT and POST methods for other than byte array. - httpClient = this.httpClient.newBuilder().retryOnConnectionFailure(false).build(); - } - - CompletableFuture completableFuture = new CompletableFuture<>(); - httpClient - .newCall(request) - .enqueue( - new Callback() { - @Override - public void onFailure(final Call call, IOException e) { - completableFuture.completeExceptionally(e); - } - - @Override - public void onResponse(Call call, final Response response) throws IOException { - try { - onResponse(response); - } catch (Exception e) { - completableFuture.completeExceptionally(e); - } - } - - private void onResponse(final Response response) throws IOException { - String trace = - response.protocol().toString().toUpperCase(Locale.US) - + " " - + response.code() - + "\n" - + response.headers(); - traceBuilder.append(trace).append("\n"); - if (traceStream != null) traceStream.println(trace); - - if (response.isSuccessful()) { - if (traceStream != null) { - // Trace response body only if the request is not - // GetObject/ListenBucketNotification - // S3 API. - Set keys = queryParamMap.keySet(); - if ((method != Method.GET - || objectName == null - || !Collections.disjoint(keys, TRACE_QUERY_PARAMS)) - && !(keys.contains("events") - && (keys.contains("prefix") || keys.contains("suffix")))) { - ResponseBody responseBody = response.peekBody(1024 * 1024); - traceStream.println(responseBody.string()); - } - traceStream.println(END_HTTP); - } - - completableFuture.complete(response); - return; - } - - String errorXml = null; - try (ResponseBody responseBody = response.body()) { - errorXml = responseBody.string(); - } - - if (!("".equals(errorXml) && method.equals(Method.HEAD))) { - traceBuilder.append(errorXml).append("\n"); - if (traceStream != null) traceStream.println(errorXml); - } - - traceBuilder.append(END_HTTP).append("\n"); - if (traceStream != null) traceStream.println(END_HTTP); - - // Error in case of Non-XML response from server for non-HEAD requests. - String contentType = response.headers().get("content-type"); - if (!method.equals(Method.HEAD) - && (contentType == null - || !Arrays.asList(contentType.split(";")).contains("application/xml"))) { - if (response.code() == 304 && response.body().contentLength() == 0) { - completableFuture.completeExceptionally( - new ServerException( - "server failed with HTTP status code " + response.code(), - response.code(), - traceBuilder.toString())); - } - - completableFuture.completeExceptionally( - new InvalidResponseException( - response.code(), - contentType, - errorXml.substring( - 0, errorXml.length() > 1024 ? 1024 : errorXml.length()), - traceBuilder.toString())); - return; - } - - ErrorResponse errorResponse = null; - if (!"".equals(errorXml)) { - try { - errorResponse = Xml.unmarshal(ErrorResponse.class, errorXml); - } catch (XmlParserException e) { - completableFuture.completeExceptionally(e); - return; - } - } else if (!method.equals(Method.HEAD)) { - completableFuture.completeExceptionally( - new InvalidResponseException( - response.code(), contentType, errorXml, traceBuilder.toString())); - return; - } - - if (errorResponse == null) { - String code = null; - String message = null; - switch (response.code()) { - case 301: - case 307: - case 400: - String[] result = handleRedirectResponse(method, bucketName, response, true); - code = result[0]; - message = result[1]; - break; - case 404: - if (objectName != null) { - code = "NoSuchKey"; - message = "Object does not exist"; - } else if (bucketName != null) { - code = NO_SUCH_BUCKET; - message = NO_SUCH_BUCKET_MESSAGE; - } else { - code = "ResourceNotFound"; - message = "Request resource not found"; - } - break; - case 501: - case 405: - code = "MethodNotAllowed"; - message = "The specified method is not allowed against this resource"; - break; - case 409: - if (bucketName != null) { - code = NO_SUCH_BUCKET; - message = NO_SUCH_BUCKET_MESSAGE; - } else { - code = "ResourceConflict"; - message = "Request resource conflicts"; - } - break; - case 403: - code = "AccessDenied"; - message = "Access denied"; - break; - case 412: - code = "PreconditionFailed"; - message = "At least one of the preconditions you specified did not hold"; - break; - case 416: - code = "InvalidRange"; - message = "The requested range cannot be satisfied"; - break; - default: - completableFuture.completeExceptionally( - new ServerException( - "server failed with HTTP status code " + response.code(), - response.code(), - traceBuilder.toString())); - return; - } - - errorResponse = - new ErrorResponse( - code, - message, - bucketName, - objectName, - request.url().encodedPath(), - response.header("x-amz-request-id"), - response.header("x-amz-id-2")); - } - - // invalidate region cache if needed - if (errorResponse.code().equals(NO_SUCH_BUCKET) - || errorResponse.code().equals(RETRY_HEAD)) { - regionCache.remove(bucketName); - } - - ErrorResponseException e = - new ErrorResponseException(errorResponse, response, traceBuilder.toString()); - completableFuture.completeExceptionally(e); - } - }); - return completableFuture; - } - - /** Execute HTTP request asynchronously for given args and parameters. */ - protected CompletableFuture executeAsync( - Method method, - BaseArgs args, - Multimap headers, - Multimap queryParams, - Object body, - int length) - throws InsufficientDataException, InternalException, InvalidKeyException, IOException, - NoSuchAlgorithmException, XmlParserException { - final Multimap extraHeaders; - final Multimap extraQueryParams; - final String bucketName; - final String region; - final String objectName; - - if (args != null) { - extraHeaders = args.extraHeaders(); - extraQueryParams = args.extraQueryParams(); - - if (args instanceof BucketArgs) { - bucketName = ((BucketArgs) args).bucket(); - region = ((BucketArgs) args).region(); - } else { - bucketName = null; - region = null; - } - - if (args instanceof ObjectArgs) { - objectName = ((ObjectArgs) args).object(); - } else { - objectName = null; - } - } else { - extraHeaders = null; - extraQueryParams = null; - bucketName = null; - region = null; - objectName = null; - } - - return getRegionAsync(bucketName, region) - .thenCompose( - location -> { - try { - return executeAsync( - method, - bucketName, - objectName, - location, - httpHeaders(merge(extraHeaders, headers)), - merge(extraQueryParams, queryParams), - body, - length); - } catch (InsufficientDataException - | InternalException - | InvalidKeyException - | IOException - | NoSuchAlgorithmException - | XmlParserException e) { - throw new CompletionException(e); - } - }); - } - - /** - * Execute HTTP request for given parameters. - * - * @deprecated This method is no longer supported. Use {@link #executeAsync}. - */ - @Deprecated - protected Response execute( - Method method, - String bucketName, - String objectName, - String region, - Headers headers, - Multimap queryParamMap, - Object body, - int length) - throws ErrorResponseException, InsufficientDataException, InternalException, - InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException, - ServerException, XmlParserException { - CompletableFuture completableFuture = - executeAsync(method, bucketName, objectName, region, headers, queryParamMap, body, length); - try { - return completableFuture.get(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - throwEncapsulatedException(e); - return null; - } - } - - /** - * Execute HTTP request for given args and parameters. - * - * @deprecated This method is no longer supported. Use {@link #executeAsync}. - */ - @Deprecated - protected Response execute( - Method method, - BaseArgs args, - Multimap headers, - Multimap queryParams, - Object body, - int length) - throws ErrorResponseException, InsufficientDataException, InternalException, - InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException, - ServerException, XmlParserException { - String bucketName = null; - String region = null; - String objectName = null; - - if (args instanceof BucketArgs) { - bucketName = ((BucketArgs) args).bucket(); - region = ((BucketArgs) args).region(); - } - - if (args instanceof ObjectArgs) objectName = ((ObjectArgs) args).object(); - - return execute( - method, - bucketName, - objectName, - getRegion(bucketName, region), - httpHeaders(merge(args.extraHeaders(), headers)), - merge(args.extraQueryParams(), queryParams), - body, - length); - } - - /** Returns region of given bucket either from region cache or set in constructor. */ - protected CompletableFuture getRegionAsync(String bucketName, String region) - throws InsufficientDataException, InternalException, InvalidKeyException, IOException, - NoSuchAlgorithmException, XmlParserException { - if (region != null) { - // Error out if region does not match with region passed via constructor. - if (this.region != null && !this.region.equals(region)) { - throw new IllegalArgumentException( - "region must be " + this.region + ", but passed " + region); - } - return CompletableFuture.completedFuture(region); - } - - if (this.region != null && !this.region.equals("")) { - return CompletableFuture.completedFuture(this.region); - } - if (bucketName == null || this.provider == null) { - return CompletableFuture.completedFuture(US_EAST_1); - } - region = regionCache.get(bucketName); - if (region != null) return CompletableFuture.completedFuture(region); - - // Execute GetBucketLocation REST API to get region of the bucket. - CompletableFuture future = - executeAsync( - Method.GET, bucketName, null, US_EAST_1, null, newMultimap("location", null), null, 0); - return future.thenApply( - response -> { - String location; - try (ResponseBody body = response.body()) { - LocationConstraint lc = Xml.unmarshal(LocationConstraint.class, body.charStream()); - if (lc.location() == null || lc.location().equals("")) { - location = US_EAST_1; - } else if (lc.location().equals("EU") && this.awsDomainSuffix != null) { - location = "eu-west-1"; // eu-west-1 is also referred as 'EU'. - } else { - location = lc.location(); - } - } catch (XmlParserException e) { - throw new CompletionException(e); - } - - regionCache.put(bucketName, location); - return location; - }); - } - - /** - * Returns region of given bucket either from region cache or set in constructor. - * - * @deprecated This method is no longer supported. Use {@link #getRegionAsync}. - */ - @Deprecated - protected String getRegion(String bucketName, String region) - throws ErrorResponseException, InsufficientDataException, InternalException, - InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException, - ServerException, XmlParserException { - try { - return getRegionAsync(bucketName, region).get(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - throwEncapsulatedException(e); - return null; - } - } - - /** Execute asynchronously GET HTTP request for given parameters. */ - protected CompletableFuture executeGetAsync( - BaseArgs args, Multimap headers, Multimap queryParams) - throws InsufficientDataException, InternalException, InvalidKeyException, IOException, - NoSuchAlgorithmException, XmlParserException { - return executeAsync(Method.GET, args, headers, queryParams, null, 0); - } - - /** - * Execute GET HTTP request for given parameters. - * - * @deprecated This method is no longer supported. Use {@link #executeGetAsync}. - */ - @Deprecated - protected Response executeGet( - BaseArgs args, Multimap headers, Multimap queryParams) - throws ErrorResponseException, InsufficientDataException, InternalException, - InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException, - ServerException, XmlParserException { - try { - return executeGetAsync(args, headers, queryParams).get(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - throwEncapsulatedException(e); - return null; - } - } - - /** Execute asynchronously HEAD HTTP request for given parameters. */ - protected CompletableFuture executeHeadAsync( - BaseArgs args, Multimap headers, Multimap queryParams) - throws InsufficientDataException, InternalException, InvalidKeyException, IOException, - NoSuchAlgorithmException, XmlParserException { - return executeAsync(Method.HEAD, args, headers, queryParams, null, 0) - .exceptionally( - e -> { - if (e instanceof ErrorResponseException) { - ErrorResponseException ex = (ErrorResponseException) e; - if (ex.errorResponse().code().equals(RETRY_HEAD)) { - return null; - } - } - throw new CompletionException(e); - }) - .thenCompose( - response -> { - if (response != null) { - return CompletableFuture.completedFuture(response); - } - - try { - return executeAsync(Method.HEAD, args, headers, queryParams, null, 0); - } catch (InsufficientDataException - | InternalException - | InvalidKeyException - | IOException - | NoSuchAlgorithmException - | XmlParserException e) { - throw new CompletionException(e); - } - }); - } - - /** - * Execute HEAD HTTP request for given parameters. - * - * @deprecated This method is no longer supported. Use {@link #executeHeadAsync}. - */ - @Deprecated - protected Response executeHead( - BaseArgs args, Multimap headers, Multimap queryParams) - throws ErrorResponseException, InsufficientDataException, InternalException, - InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException, - ServerException, XmlParserException { - try { - return executeHeadAsync(args, headers, queryParams).get(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - throwEncapsulatedException(e); - return null; - } - } - - /** Execute asynchronously DELETE HTTP request for given parameters. */ - protected CompletableFuture executeDeleteAsync( - BaseArgs args, Multimap headers, Multimap queryParams) - throws InsufficientDataException, InternalException, InvalidKeyException, IOException, - NoSuchAlgorithmException, XmlParserException { - return executeAsync(Method.DELETE, args, headers, queryParams, null, 0) - .thenApply( - response -> { - if (response != null) response.body().close(); - return response; - }); - } - - /** - * Execute DELETE HTTP request for given parameters. - * - * @deprecated This method is no longer supported. Use {@link #executeDeleteAsync}. - */ - @Deprecated - protected Response executeDelete( - BaseArgs args, Multimap headers, Multimap queryParams) - throws ErrorResponseException, InsufficientDataException, InternalException, - InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException, - ServerException, XmlParserException { - try { - return executeDeleteAsync(args, headers, queryParams).get(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - throwEncapsulatedException(e); - return null; - } - } - - /** Execute asynchronously POST HTTP request for given parameters. */ - protected CompletableFuture executePostAsync( - BaseArgs args, - Multimap headers, - Multimap queryParams, - Object data) - throws InsufficientDataException, InternalException, InvalidKeyException, IOException, - NoSuchAlgorithmException, XmlParserException { - return executeAsync(Method.POST, args, headers, queryParams, data, 0); - } - - /** - * Execute POST HTTP request for given parameters. - * - * @deprecated This method is no longer supported. Use {@link #executePostAsync}. - */ - @Deprecated - protected Response executePost( - BaseArgs args, - Multimap headers, - Multimap queryParams, - Object data) - throws ErrorResponseException, InsufficientDataException, InternalException, - InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException, - ServerException, XmlParserException { - try { - return executePostAsync(args, headers, queryParams, data).get(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - throwEncapsulatedException(e); - return null; - } - } - - /** Execute asynchronously PUT HTTP request for given parameters. */ - protected CompletableFuture executePutAsync( - BaseArgs args, - Multimap headers, - Multimap queryParams, - Object data, - int length) - throws InsufficientDataException, InternalException, InvalidKeyException, IOException, - NoSuchAlgorithmException, XmlParserException { - return executeAsync(Method.PUT, args, headers, queryParams, data, length); - } - - /** - * Execute PUT HTTP request for given parameters. - * - * @deprecated This method is no longer supported. Use {@link #executePutAsync}. - */ - @Deprecated - protected Response executePut( - BaseArgs args, - Multimap headers, - Multimap queryParams, - Object data, - int length) - throws ErrorResponseException, InsufficientDataException, InternalException, - InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException, - ServerException, XmlParserException { - try { - return executePutAsync(args, headers, queryParams, data, length).get(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - throwEncapsulatedException(e); - return null; - } - } - - protected CompletableFuture calculatePartCountAsync(List sources) - throws InsufficientDataException, InternalException, InvalidKeyException, IOException, - NoSuchAlgorithmException, XmlParserException { - long[] objectSize = {0}; - int index = 0; - - CompletableFuture completableFuture = CompletableFuture.supplyAsync(() -> 0); - for (ComposeSource src : sources) { - index++; - final int i = index; - completableFuture = - completableFuture.thenCombine( - statObjectAsync(new StatObjectArgs((ObjectReadArgs) src)), - (partCount, statObjectResponse) -> { - src.buildHeaders(statObjectResponse.size(), statObjectResponse.etag()); - - long size = statObjectResponse.size(); - if (src.length() != null) { - size = src.length(); - } else if (src.offset() != null) { - size -= src.offset(); - } - - if (size < ObjectWriteArgs.MIN_MULTIPART_SIZE - && sources.size() != 1 - && i != sources.size()) { - throw new IllegalArgumentException( - "source " - + src.bucket() - + "/" - + src.object() - + ": size " - + size - + " must be greater than " - + ObjectWriteArgs.MIN_MULTIPART_SIZE); - } - - objectSize[0] += size; - if (objectSize[0] > ObjectWriteArgs.MAX_OBJECT_SIZE) { - throw new IllegalArgumentException( - "destination object size must be less than " - + ObjectWriteArgs.MAX_OBJECT_SIZE); - } - - if (size > ObjectWriteArgs.MAX_PART_SIZE) { - long count = size / ObjectWriteArgs.MAX_PART_SIZE; - long lastPartSize = size - (count * ObjectWriteArgs.MAX_PART_SIZE); - if (lastPartSize > 0) { - count++; - } else { - lastPartSize = ObjectWriteArgs.MAX_PART_SIZE; - } - - if (lastPartSize < ObjectWriteArgs.MIN_MULTIPART_SIZE - && sources.size() != 1 - && i != sources.size()) { - throw new IllegalArgumentException( - "source " - + src.bucket() - + "/" - + src.object() - + ": " - + "for multipart split upload of " - + size - + ", last part size is less than " - + ObjectWriteArgs.MIN_MULTIPART_SIZE); - } - partCount += (int) count; - } else { - partCount++; - } - - if (partCount > ObjectWriteArgs.MAX_MULTIPART_COUNT) { - throw new IllegalArgumentException( - "Compose sources create more than allowed multipart count " - + ObjectWriteArgs.MAX_MULTIPART_COUNT); - } - return partCount; - }); - } - - return completableFuture; - } - - /** Calculate part count of given compose sources. */ - @Deprecated - protected int calculatePartCount(List sources) - throws ErrorResponseException, InsufficientDataException, InternalException, - InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException, - ServerException, XmlParserException { - long objectSize = 0; - int partCount = 0; - int i = 0; - for (ComposeSource src : sources) { - i++; - StatObjectResponse stat = null; - try { - stat = statObjectAsync(new StatObjectArgs((ObjectReadArgs) src)).get(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - throwEncapsulatedException(e); - } - - src.buildHeaders(stat.size(), stat.etag()); - - long size = stat.size(); - if (src.length() != null) { - size = src.length(); - } else if (src.offset() != null) { - size -= src.offset(); - } - - if (size < ObjectWriteArgs.MIN_MULTIPART_SIZE && sources.size() != 1 && i != sources.size()) { - throw new IllegalArgumentException( - "source " - + src.bucket() - + "/" - + src.object() - + ": size " - + size - + " must be greater than " - + ObjectWriteArgs.MIN_MULTIPART_SIZE); - } - - objectSize += size; - if (objectSize > ObjectWriteArgs.MAX_OBJECT_SIZE) { - throw new IllegalArgumentException( - "destination object size must be less than " + ObjectWriteArgs.MAX_OBJECT_SIZE); - } - - if (size > ObjectWriteArgs.MAX_PART_SIZE) { - long count = size / ObjectWriteArgs.MAX_PART_SIZE; - long lastPartSize = size - (count * ObjectWriteArgs.MAX_PART_SIZE); - if (lastPartSize > 0) { - count++; - } else { - lastPartSize = ObjectWriteArgs.MAX_PART_SIZE; - } - - if (lastPartSize < ObjectWriteArgs.MIN_MULTIPART_SIZE - && sources.size() != 1 - && i != sources.size()) { - throw new IllegalArgumentException( - "source " - + src.bucket() - + "/" - + src.object() - + ": " - + "for multipart split upload of " - + size - + ", last part size is less than " - + ObjectWriteArgs.MIN_MULTIPART_SIZE); - } - partCount += (int) count; - } else { - partCount++; - } - - if (partCount > ObjectWriteArgs.MAX_MULTIPART_COUNT) { - throw new IllegalArgumentException( - "Compose sources create more than allowed multipart count " - + ObjectWriteArgs.MAX_MULTIPART_COUNT); - } - } - - return partCount; - } - - private abstract class ObjectIterator implements Iterator> { - protected Result error; - protected Iterator itemIterator; - protected Iterator deleteMarkerIterator; - protected Iterator prefixIterator; - protected boolean completed = false; - protected ListObjectsResult listObjectsResult; - protected String lastObjectName; - - protected abstract void populateResult() - throws ErrorResponseException, InsufficientDataException, InternalException, - InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException, - ServerException, XmlParserException; - - protected synchronized void populate() { - try { - populateResult(); - } catch (ErrorResponseException - | InsufficientDataException - | InternalException - | InvalidKeyException - | InvalidResponseException - | IOException - | NoSuchAlgorithmException - | ServerException - | XmlParserException e) { - this.error = new Result<>(e); - } - - if (this.listObjectsResult != null) { - this.itemIterator = this.listObjectsResult.contents().iterator(); - this.deleteMarkerIterator = this.listObjectsResult.deleteMarkers().iterator(); - this.prefixIterator = this.listObjectsResult.commonPrefixes().iterator(); - } else { - this.itemIterator = new LinkedList().iterator(); - this.deleteMarkerIterator = new LinkedList().iterator(); - this.prefixIterator = new LinkedList().iterator(); - } - } - - @Override - public boolean hasNext() { - if (this.completed) return false; - - if (this.error == null - && this.itemIterator == null - && this.deleteMarkerIterator == null - && this.prefixIterator == null) { - populate(); - } - - if (this.error == null - && !this.itemIterator.hasNext() - && !this.deleteMarkerIterator.hasNext() - && !this.prefixIterator.hasNext() - && this.listObjectsResult.isTruncated()) { - populate(); - } - - if (this.error != null) return true; - if (this.itemIterator.hasNext()) return true; - if (this.deleteMarkerIterator.hasNext()) return true; - if (this.prefixIterator.hasNext()) return true; - - this.completed = true; - return false; - } - - @Override - public Result next() { - if (this.completed) throw new NoSuchElementException(); - if (this.error == null - && this.itemIterator == null - && this.deleteMarkerIterator == null - && this.prefixIterator == null) { - populate(); - } - - if (this.error == null - && !this.itemIterator.hasNext() - && !this.deleteMarkerIterator.hasNext() - && !this.prefixIterator.hasNext() - && this.listObjectsResult.isTruncated()) { - populate(); - } - - if (this.error != null) { - this.completed = true; - return this.error; - } - - Item item = null; - if (this.itemIterator.hasNext()) { - item = this.itemIterator.next(); - item.setEncodingType(this.listObjectsResult.encodingType()); - this.lastObjectName = item.objectName(); - } else if (this.deleteMarkerIterator.hasNext()) { - item = this.deleteMarkerIterator.next(); - } else if (this.prefixIterator.hasNext()) { - item = this.prefixIterator.next().toItem(); - } - - if (item != null) { - item.setEncodingType(this.listObjectsResult.encodingType()); - return new Result<>(item); - } - - this.completed = true; - throw new NoSuchElementException(); - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - } - - /** Execute list objects v2. */ - protected Iterable> listObjectsV2(ListObjectsArgs args) { - return new Iterable>() { - @Override - public Iterator> iterator() { - return new ObjectIterator() { - private ListBucketResultV2 result = null; - - @Override - protected void populateResult() - throws ErrorResponseException, InsufficientDataException, InternalException, - InvalidKeyException, InvalidResponseException, IOException, - NoSuchAlgorithmException, ServerException, XmlParserException { - this.listObjectsResult = null; - this.itemIterator = null; - this.prefixIterator = null; - - try { - ListObjectsV2Response response = - listObjectsV2Async( - args.bucket(), - args.region(), - args.delimiter(), - args.useUrlEncodingType() ? "url" : null, - args.startAfter(), - args.maxKeys(), - args.prefix(), - (result == null) - ? args.continuationToken() - : result.nextContinuationToken(), - args.fetchOwner(), - args.includeUserMetadata(), - args.extraHeaders(), - args.extraQueryParams()) - .get(); - result = response.result(); - this.listObjectsResult = response.result(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - throwEncapsulatedException(e); - } - } - }; - } - }; - } - - /** Execute list objects v1. */ - protected Iterable> listObjectsV1(ListObjectsArgs args) { - return new Iterable>() { - @Override - public Iterator> iterator() { - return new ObjectIterator() { - private ListBucketResultV1 result = null; - - @Override - protected void populateResult() - throws ErrorResponseException, InsufficientDataException, InternalException, - InvalidKeyException, InvalidResponseException, IOException, - NoSuchAlgorithmException, ServerException, XmlParserException { - this.listObjectsResult = null; - this.itemIterator = null; - this.prefixIterator = null; - - String nextMarker = (result == null) ? args.marker() : result.nextMarker(); - if (nextMarker == null) nextMarker = this.lastObjectName; - - try { - ListObjectsV1Response response = - listObjectsV1Async( - args.bucket(), - args.region(), - args.delimiter(), - args.useUrlEncodingType() ? "url" : null, - nextMarker, - args.maxKeys(), - args.prefix(), - args.extraHeaders(), - args.extraQueryParams()) - .get(); - result = response.result(); - this.listObjectsResult = response.result(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - throwEncapsulatedException(e); - } - } - }; - } - }; - } - - /** Execute list object versions. */ - protected Iterable> listObjectVersions(ListObjectsArgs args) { - return new Iterable>() { - @Override - public Iterator> iterator() { - return new ObjectIterator() { - private ListVersionsResult result = null; - - @Override - protected void populateResult() - throws ErrorResponseException, InsufficientDataException, InternalException, - InvalidKeyException, InvalidResponseException, IOException, - NoSuchAlgorithmException, ServerException, XmlParserException { - this.listObjectsResult = null; - this.itemIterator = null; - this.prefixIterator = null; - - try { - ListObjectVersionsResponse response = - listObjectVersionsAsync( - args.bucket(), - args.region(), - args.delimiter(), - args.useUrlEncodingType() ? "url" : null, - (result == null) ? args.keyMarker() : result.nextKeyMarker(), - args.maxKeys(), - args.prefix(), - (result == null) ? args.versionIdMarker() : result.nextVersionIdMarker(), - args.extraHeaders(), - args.extraQueryParams()) - .get(); - result = response.result(); - this.listObjectsResult = response.result(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - throwEncapsulatedException(e); - } - } - }; - } - }; - } - - protected PartReader newPartReader(Object data, long objectSize, long partSize, int partCount) { - if (data instanceof RandomAccessFile) { - return new PartReader((RandomAccessFile) data, objectSize, partSize, partCount); - } - - if (data instanceof InputStream) { - return new PartReader((InputStream) data, objectSize, partSize, partCount); - } - - return null; - } - - /** - * Execute put object. - * - * @deprecated This method is no longer supported. Use {@link #putObjectAsync}. - */ - @Deprecated - protected ObjectWriteResponse putObject( - PutObjectBaseArgs args, - Object data, - long objectSize, - long partSize, - int partCount, - String contentType) - throws ErrorResponseException, InsufficientDataException, InternalException, - InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException, - ServerException, XmlParserException { - try { - return putObjectAsync(args, data, objectSize, partSize, partCount, contentType).get(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - throwEncapsulatedException(e); - return null; - } - } - - /** Notification result records representation. */ - protected static class NotificationResultRecords { - Response response = null; - Scanner scanner = null; - ObjectMapper mapper = null; - - public NotificationResultRecords(Response response) { - this.response = response; - this.scanner = new Scanner(response.body().charStream()).useDelimiter("\n"); - this.mapper = - JsonMapper.builder() - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true) - .build(); - } - - /** returns closeable iterator of result of notification records. */ - public CloseableIterator> closeableIterator() { - return new CloseableIterator>() { - String recordsString = null; - NotificationRecords records = null; - boolean isClosed = false; - - @Override - public void close() throws IOException { - if (!isClosed) { - try { - response.body().close(); - scanner.close(); - } finally { - isClosed = true; - } - } - } - - public boolean populate() { - if (isClosed) return false; - if (recordsString != null) return true; - - while (scanner.hasNext()) { - recordsString = scanner.next().trim(); - if (!recordsString.equals("")) break; - } - - if (recordsString == null || recordsString.equals("")) { - try { - close(); - } catch (IOException e) { - isClosed = true; - } - return false; - } - return true; - } - - @Override - public boolean hasNext() { - return populate(); - } - - @Override - public Result next() { - if (isClosed) throw new NoSuchElementException(); - if ((recordsString == null || recordsString.equals("")) && !populate()) { - throw new NoSuchElementException(); - } - - try { - records = mapper.readValue(recordsString, NotificationRecords.class); - return new Result<>(records); - } catch (JsonMappingException e) { - return new Result<>(e); - } catch (JsonParseException e) { - return new Result<>(e); - } catch (IOException e) { - return new Result<>(e); - } finally { - recordsString = null; - records = null; - } - } - }; - } - } - - private Multimap getCommonListObjectsQueryParams( - String delimiter, String encodingType, Integer maxKeys, String prefix) { - Multimap queryParams = - newMultimap( - "delimiter", - (delimiter == null) ? "" : delimiter, - "max-keys", - Integer.toString(maxKeys > 0 ? maxKeys : 1000), - "prefix", - (prefix == null) ? "" : prefix); - if (encodingType != null) queryParams.put("encoding-type", encodingType); - return queryParams; - } - - /** - * 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. - * - *
Example:{@code
-   * minioClient.setTimeout(TimeUnit.SECONDS.toMillis(10), TimeUnit.SECONDS.toMillis(10),
-   *     TimeUnit.SECONDS.toMillis(30));
-   * }
- * - * @param connectTimeout HTTP connect timeout in milliseconds. - * @param writeTimeout HTTP write timeout in milliseconds. - * @param readTimeout HTTP read timeout in milliseconds. - */ - public void setTimeout(long connectTimeout, long writeTimeout, long readTimeout) { - this.httpClient = - HttpUtils.setTimeout(this.httpClient, connectTimeout, writeTimeout, readTimeout); - } - - /** - * Ignores check on server certificate for HTTPS connection. - * - *
Example:{@code
-   * minioClient.ignoreCertCheck();
-   * }
- * - * @throws KeyManagementException thrown to indicate key management error. - * @throws NoSuchAlgorithmException thrown to indicate missing of SSL library. - */ - @SuppressFBWarnings(value = "SIC", justification = "Should not be used in production anyways.") - public void ignoreCertCheck() throws KeyManagementException, NoSuchAlgorithmException { - this.httpClient = HttpUtils.disableCertCheck(this.httpClient); - } - - /** - * Sets application's name/version to user agent. For more information about user agent refer #rfc2616. - * - * @param name Your application name. - * @param version Your application version. - */ - public void setAppInfo(String name, String version) { - if (name == null || version == null) return; - this.userAgent = - MinioProperties.INSTANCE.getDefaultUserAgent() + " " + name.trim() + "/" + version.trim(); - } - - /** - * Enables HTTP call tracing and written to traceStream. - * - * @param traceStream {@link OutputStream} for writing HTTP call tracing. - * @see #traceOff - */ - public void traceOn(OutputStream traceStream) { - if (traceStream == null) throw new IllegalArgumentException("trace stream must be provided"); - this.traceStream = - new PrintWriter(new OutputStreamWriter(traceStream, StandardCharsets.UTF_8), true); - } - - /** - * Disables HTTP call tracing previously enabled. - * - * @see #traceOn - * @throws IOException upon connection error - */ - public void traceOff() throws IOException { - this.traceStream = null; - } - - /** - * Enables accelerate endpoint for Amazon S3 endpoint. - * - * @deprecated This method is no longer supported. - */ - @Deprecated - public void enableAccelerateEndpoint() { - this.awsS3Prefix = "s3-accelerate."; - } - - /** - * Disables accelerate endpoint for Amazon S3 endpoint. - * - * @deprecated This method is no longer supported. - */ - @Deprecated - public void disableAccelerateEndpoint() { - this.awsS3Prefix = "s3."; - } - - /** Enables dual-stack endpoint for Amazon S3 endpoint. */ - public void enableDualStackEndpoint() { - this.awsDualstack = true; - } - - /** Disables dual-stack endpoint for Amazon S3 endpoint. */ - public void disableDualStackEndpoint() { - this.awsDualstack = false; - } - - /** Enables virtual-style endpoint. */ - public void enableVirtualStyleEndpoint() { - this.useVirtualStyle = true; - } - - /** Disables virtual-style endpoint. */ - public void disableVirtualStyleEndpoint() { - this.useVirtualStyle = false; - } - - /** Sets AWS S3 domain prefix. */ - public void setAwsS3Prefix(@Nonnull String awsS3Prefix) { - if (awsS3Prefix == null) throw new IllegalArgumentException("null Amazon AWS S3 domain prefix"); - if (!HttpUtils.AWS_S3_PREFIX_REGEX.matcher(awsS3Prefix).find()) { - throw new IllegalArgumentException("invalid Amazon AWS S3 domain prefix " + awsS3Prefix); - } - this.awsS3Prefix = awsS3Prefix; - } - - /** Execute stat object a.k.a head object S3 API asynchronously. */ - protected CompletableFuture statObjectAsync(StatObjectArgs args) - throws InsufficientDataException, InternalException, InvalidKeyException, IOException, - NoSuchAlgorithmException, XmlParserException { - checkArgs(args); - args.validateSsec(baseUrl); - return executeHeadAsync( - args, - args.getHeaders(), - (args.versionId() != null) ? newMultimap("versionId", args.versionId()) : null) - .thenApply( - response -> - new StatObjectResponse( - response.headers(), args.bucket(), args.region(), args.object())); - } - - /** - * Do AbortMultipartUpload - * S3 API asynchronously. - * - * @param bucketName Name of the bucket. - * @param region Region of the bucket. - * @param objectName Object name in the bucket. - * @param uploadId Upload ID. - * @param extraHeaders Extra headers (Optional). - * @param extraQueryParams Extra query parameters (Optional). - * @return {@link CompletableFuture}<{@link AbortMultipartUploadResponse}> 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 abortMultipartUploadAsync( - String bucketName, - String region, - String objectName, - String uploadId, - Multimap extraHeaders, - Multimap extraQueryParams) - throws InsufficientDataException, InternalException, InvalidKeyException, IOException, - NoSuchAlgorithmException, XmlParserException { - return getRegionAsync(bucketName, region) - .thenCompose( - location -> { - try { - return executeAsync( - Method.DELETE, - bucketName, - objectName, - location, - httpHeaders(extraHeaders), - merge(extraQueryParams, newMultimap(UPLOAD_ID, uploadId)), - null, - 0); - } catch (InsufficientDataException - | InternalException - | InvalidKeyException - | IOException - | NoSuchAlgorithmException - | XmlParserException e) { - throw new CompletionException(e); - } - }) - .thenApply( - response -> { - try { - return new AbortMultipartUploadResponse( - response.headers(), bucketName, region, objectName, uploadId); - } finally { - response.close(); - } - }); - } - - /** - * Do AbortMultipartUpload - * S3 API. - * - * @param bucketName Name of the bucket. - * @param region Region of the bucket. - * @param objectName Object name in the bucket. - * @param uploadId Upload ID. - * @param extraHeaders Extra headers (Optional). - * @param extraQueryParams Extra query parameters (Optional). - * @return {@link AbortMultipartUploadResponse} 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. - * @deprecated This method is no longer supported. Use {@link #abortMultipartUploadAsync}. - */ - @Deprecated - protected AbortMultipartUploadResponse abortMultipartUpload( - String bucketName, - String region, - String objectName, - String uploadId, - Multimap extraHeaders, - Multimap extraQueryParams) - throws NoSuchAlgorithmException, InsufficientDataException, IOException, InvalidKeyException, - ServerException, XmlParserException, ErrorResponseException, InternalException, - InvalidResponseException { - try { - return abortMultipartUploadAsync( - bucketName, region, objectName, uploadId, extraHeaders, extraQueryParams) - .get(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - throwEncapsulatedException(e); - return null; - } - } - - /** - * Do CompleteMultipartUpload - * S3 API asynchronously. - * - * @param bucketName Name of the bucket. - * @param region Region of the bucket. - * @param objectName Object name in the bucket. - * @param uploadId Upload ID. - * @param parts List of parts. - * @param extraHeaders Extra headers (Optional). - * @param extraQueryParams Extra query parameters (Optional). - * @return {@link CompletableFuture}<{@link ObjectWriteResponse}> object. - * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. - * @throws InternalException thrown to indicate internal library error. - * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. - * @throws IOException thrown to indicate I/O error on S3 operation. - * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. - * @throws XmlParserException thrown to indicate XML parsing error. - */ - public CompletableFuture completeMultipartUploadAsync( - String bucketName, - String region, - String objectName, - String uploadId, - Part[] parts, - Multimap extraHeaders, - Multimap extraQueryParams) - throws InsufficientDataException, InternalException, InvalidKeyException, IOException, - NoSuchAlgorithmException, XmlParserException { - Multimap queryParams = newMultimap(extraQueryParams); - queryParams.put(UPLOAD_ID, uploadId); - return getRegionAsync(bucketName, region) - .thenCompose( - location -> { - try { - return executeAsync( - Method.POST, - bucketName, - objectName, - location, - httpHeaders(extraHeaders), - queryParams, - new CompleteMultipartUpload(parts), - 0); - } catch (InsufficientDataException - | InternalException - | InvalidKeyException - | IOException - | NoSuchAlgorithmException - | XmlParserException e) { - throw new CompletionException(e); - } - }) - .thenApply( - response -> { - try { - String bodyContent = response.body().string(); - bodyContent = bodyContent.trim(); - if (!bodyContent.isEmpty()) { - try { - if (Xml.validate(ErrorResponse.class, bodyContent)) { - ErrorResponse errorResponse = Xml.unmarshal(ErrorResponse.class, bodyContent); - throw new CompletionException( - new ErrorResponseException(errorResponse, response, null)); - } - } catch (XmlParserException e) { - // As it is not message, fallback to parse CompleteMultipartUploadOutput - // XML. - } - - try { - CompleteMultipartUploadResult result = - Xml.unmarshal(CompleteMultipartUploadResult.class, bodyContent); - return new ObjectWriteResponse( - response.headers(), - result.bucket(), - result.location(), - result.object(), - result.etag(), - response.header("x-amz-version-id"), - result); - } catch (XmlParserException e) { - // As this CompleteMultipartUpload REST call succeeded, just log it. - Logger.getLogger(S3Base.class.getName()) - .warning( - "S3 service returned unknown XML for CompleteMultipartUpload REST API. " - + bodyContent); - } - } - - return new ObjectWriteResponse( - response.headers(), - bucketName, - region, - objectName, - null, - response.header("x-amz-version-id")); - } catch (IOException e) { - throw new CompletionException(e); - } finally { - response.close(); - } - }); - } - - /** - * Do CompleteMultipartUpload - * S3 API. - * - * @param bucketName Name of the bucket. - * @param region Region of the bucket. - * @param objectName Object name in the bucket. - * @param uploadId Upload ID. - * @param parts List of parts. - * @param extraHeaders Extra headers (Optional). - * @param extraQueryParams Extra query parameters (Optional). - * @return {@link ObjectWriteResponse} 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. - * @deprecated This method is no longer supported. Use {@link #completeMultipartUploadAsync}. - */ - @Deprecated - protected ObjectWriteResponse completeMultipartUpload( - String bucketName, - String region, - String objectName, - String uploadId, - Part[] parts, - Multimap extraHeaders, - Multimap extraQueryParams) - throws NoSuchAlgorithmException, InsufficientDataException, IOException, InvalidKeyException, - ServerException, XmlParserException, ErrorResponseException, InternalException, - InvalidResponseException { - try { - return completeMultipartUploadAsync( - bucketName, region, objectName, uploadId, parts, extraHeaders, extraQueryParams) - .get(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - throwEncapsulatedException(e); - return null; - } - } - - /** - * Do CreateMultipartUpload - * S3 API asynchronously. - * - * @param bucketName Name of the bucket. - * @param region Region name of buckets in S3 service. - * @param objectName Object name in the bucket. - * @param headers Request headers. - * @param extraQueryParams Extra query parameters for request (Optional). - * @return {@link CompletableFuture}<{@link CreateMultipartUploadResponse}> 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 createMultipartUploadAsync( - String bucketName, - String region, - String objectName, - Multimap headers, - Multimap extraQueryParams) - throws InsufficientDataException, InternalException, InvalidKeyException, IOException, - NoSuchAlgorithmException, XmlParserException { - Multimap queryParams = newMultimap(extraQueryParams); - queryParams.put("uploads", ""); - - Multimap headersCopy = newMultimap(headers); - // set content type if not set already - if (!headersCopy.containsKey("Content-Type")) { - headersCopy.put("Content-Type", "application/octet-stream"); - } - - return getRegionAsync(bucketName, region) - .thenCompose( - location -> { - try { - return executeAsync( - Method.POST, - bucketName, - objectName, - location, - httpHeaders(headersCopy), - queryParams, - null, - 0); - } catch (InsufficientDataException - | InternalException - | InvalidKeyException - | IOException - | NoSuchAlgorithmException - | XmlParserException e) { - throw new CompletionException(e); - } - }) - .thenApply( - response -> { - try { - InitiateMultipartUploadResult result = - Xml.unmarshal( - InitiateMultipartUploadResult.class, response.body().charStream()); - return new CreateMultipartUploadResponse( - response.headers(), bucketName, region, objectName, result); - } catch (XmlParserException e) { - throw new CompletionException(e); - } finally { - response.close(); - } - }); - } - - /** - * Do CreateMultipartUpload - * S3 API. - * - * @param bucketName Name of the bucket. - * @param region Region name of buckets in S3 service. - * @param objectName Object name in the bucket. - * @param headers Request headers. - * @param extraQueryParams Extra query parameters for request (Optional). - * @return {@link CreateMultipartUploadResponse} 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. - * @deprecated This method is no longer supported. Use {@link #createMultipartUploadAsync}. - */ - @Deprecated - protected CreateMultipartUploadResponse createMultipartUpload( - String bucketName, - String region, - String objectName, - Multimap headers, - Multimap extraQueryParams) - throws NoSuchAlgorithmException, InsufficientDataException, IOException, InvalidKeyException, - ServerException, XmlParserException, ErrorResponseException, InternalException, - InvalidResponseException { - try { - return createMultipartUploadAsync(bucketName, region, objectName, headers, extraQueryParams) - .get(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - throwEncapsulatedException(e); - return null; - } - } - - /** - * Do DeleteObjects S3 - * API asynchronously. - * - * @param bucketName Name of the bucket. - * @param region Region of the bucket (Optional). - * @param objectList List of object names. - * @param quiet Quiet flag. - * @param bypassGovernanceMode Bypass Governance retention mode. - * @param extraHeaders Extra headers for request (Optional). - * @param extraQueryParams Extra query parameters for request (Optional). - * @return {@link CompletableFuture}<{@link DeleteObjectsResponse}> 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. - */ - protected CompletableFuture deleteObjectsAsync( - String bucketName, - String region, - List objectList, - boolean quiet, - boolean bypassGovernanceMode, - Multimap extraHeaders, - Multimap extraQueryParams) - throws InsufficientDataException, InternalException, InvalidKeyException, IOException, - NoSuchAlgorithmException, XmlParserException { - if (objectList == null) objectList = new LinkedList<>(); - - if (objectList.size() > 1000) { - throw new IllegalArgumentException("list of objects must not be more than 1000"); - } - - Multimap headers = - merge( - extraHeaders, - bypassGovernanceMode ? newMultimap("x-amz-bypass-governance-retention", "true") : null); - - final List objects = objectList; - return getRegionAsync(bucketName, region) - .thenCompose( - location -> { - try { - return executeAsync( - Method.POST, - bucketName, - null, - location, - httpHeaders(headers), - merge(extraQueryParams, newMultimap("delete", "")), - new DeleteRequest(objects, quiet), - 0); - } catch (InsufficientDataException - | InternalException - | InvalidKeyException - | IOException - | NoSuchAlgorithmException - | XmlParserException e) { - throw new CompletionException(e); - } - }) - .thenApply( - response -> { - try { - String bodyContent = response.body().string(); - try { - if (Xml.validate(DeleteError.class, bodyContent)) { - DeleteError error = Xml.unmarshal(DeleteError.class, bodyContent); - DeleteResult result = new DeleteResult(error); - return new DeleteObjectsResponse( - response.headers(), bucketName, region, result); - } - } catch (XmlParserException e) { - // Ignore this exception as it is not message, - // but parse it as message below. - } - - DeleteResult result = Xml.unmarshal(DeleteResult.class, bodyContent); - return new DeleteObjectsResponse(response.headers(), bucketName, region, result); - } catch (IOException | XmlParserException e) { - throw new CompletionException(e); - } finally { - response.close(); - } - }); - } - - /** - * Do DeleteObjects S3 - * API. - * - * @param bucketName Name of the bucket. - * @param region Region of the bucket (Optional). - * @param objectList List of object names. - * @param quiet Quiet flag. - * @param bypassGovernanceMode Bypass Governance retention mode. - * @param extraHeaders Extra headers for request (Optional). - * @param extraQueryParams Extra query parameters for request (Optional). - * @return {@link DeleteObjectsResponse} 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. - * @deprecated This method is no longer supported. Use {@link #deleteObjectsAsync}. - */ - @Deprecated - protected DeleteObjectsResponse deleteObjects( - String bucketName, - String region, - List objectList, - boolean quiet, - boolean bypassGovernanceMode, - Multimap extraHeaders, - Multimap extraQueryParams) - throws NoSuchAlgorithmException, InsufficientDataException, IOException, InvalidKeyException, - ServerException, XmlParserException, ErrorResponseException, InternalException, - InvalidResponseException { - try { - return deleteObjectsAsync( - bucketName, - region, - objectList, - quiet, - bypassGovernanceMode, - extraHeaders, - extraQueryParams) - .get(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - throwEncapsulatedException(e); - return null; - } - } - - /** - * Do ListObjects - * version 2 S3 API asynchronously. - * - * @param bucketName Name of the bucket. - * @param region Region of the bucket (Optional). - * @param delimiter Delimiter (Optional). - * @param encodingType Encoding type (Optional). - * @param startAfter Fetch listing after this key (Optional). - * @param maxKeys Maximum object information to fetch (Optional). - * @param prefix Prefix (Optional). - * @param continuationToken Continuation token (Optional). - * @param fetchOwner Flag to fetch owner information (Optional). - * @param includeUserMetadata MinIO extension flag to include user metadata (Optional). - * @param extraHeaders Extra headers for request (Optional). - * @param extraQueryParams Extra query parameters for request (Optional). - * @return {@link CompletableFuture}<{@link ListObjectsV2Response}> 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. - */ - protected CompletableFuture listObjectsV2Async( - String bucketName, - String region, - String delimiter, - String encodingType, - String startAfter, - Integer maxKeys, - String prefix, - String continuationToken, - boolean fetchOwner, - boolean includeUserMetadata, - Multimap extraHeaders, - Multimap extraQueryParams) - throws InsufficientDataException, InternalException, InvalidKeyException, IOException, - NoSuchAlgorithmException, XmlParserException { - Multimap queryParams = - merge( - extraQueryParams, - getCommonListObjectsQueryParams(delimiter, encodingType, maxKeys, prefix)); - queryParams.put("list-type", "2"); - if (continuationToken != null) queryParams.put("continuation-token", continuationToken); - if (fetchOwner) queryParams.put("fetch-owner", "true"); - if (startAfter != null) queryParams.put("start-after", startAfter); - if (includeUserMetadata) queryParams.put("metadata", "true"); - - return getRegionAsync(bucketName, region) - .thenCompose( - location -> { - try { - return executeAsync( - Method.GET, - bucketName, - null, - location, - httpHeaders(extraHeaders), - queryParams, - null, - 0); - } catch (InsufficientDataException - | InternalException - | InvalidKeyException - | IOException - | NoSuchAlgorithmException - | XmlParserException e) { - throw new CompletionException(e); - } - }) - .thenApply( - response -> { - try { - ListBucketResultV2 result = - Xml.unmarshal(ListBucketResultV2.class, response.body().charStream()); - return new ListObjectsV2Response(response.headers(), bucketName, region, result); - } catch (XmlParserException e) { - throw new CompletionException(e); - } finally { - response.close(); - } - }); - } - - /** - * Do ListObjects - * version 1 S3 API. - * - * @param bucketName Name of the bucket. - * @param region Region of the bucket (Optional). - * @param delimiter Delimiter (Optional). - * @param encodingType Encoding type (Optional). - * @param startAfter Fetch listing after this key (Optional). - * @param maxKeys Maximum object information to fetch (Optional). - * @param prefix Prefix (Optional). - * @param continuationToken Continuation token (Optional). - * @param fetchOwner Flag to fetch owner information (Optional). - * @param includeUserMetadata MinIO extension flag to include user metadata (Optional). - * @param extraHeaders Extra headers for request (Optional). - * @param extraQueryParams Extra query parameters for request (Optional). - * @return {@link ListObjectsV2Response} 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. - * @deprecated This method is no longer supported. Use {@link #listObjectsV2Async}. - */ - @Deprecated - protected ListObjectsV2Response listObjectsV2( - String bucketName, - String region, - String delimiter, - String encodingType, - String startAfter, - Integer maxKeys, - String prefix, - String continuationToken, - boolean fetchOwner, - boolean includeUserMetadata, - Multimap extraHeaders, - Multimap extraQueryParams) - throws InvalidKeyException, NoSuchAlgorithmException, InsufficientDataException, - ServerException, XmlParserException, ErrorResponseException, InternalException, - InvalidResponseException, IOException { - try { - return listObjectsV2Async( - bucketName, - region, - delimiter, - encodingType, - startAfter, - maxKeys, - prefix, - continuationToken, - fetchOwner, - includeUserMetadata, - extraHeaders, - extraQueryParams) - .get(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - throwEncapsulatedException(e); - return null; - } - } - - /** - * Do ListObjects - * version 1 S3 API asynchronously. - * - * @param bucketName Name of the bucket. - * @param region Region of the bucket (Optional). - * @param delimiter Delimiter (Optional). - * @param encodingType Encoding type (Optional). - * @param marker Marker (Optional). - * @param maxKeys Maximum object information to fetch (Optional). - * @param prefix Prefix (Optional). - * @param extraHeaders Extra headers for request (Optional). - * @param extraQueryParams Extra query parameters for request (Optional). - * @return {@link CompletableFuture}<{@link ListObjectsV1Response}> 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. - */ - protected CompletableFuture listObjectsV1Async( - String bucketName, - String region, - String delimiter, - String encodingType, - String marker, - Integer maxKeys, - String prefix, - Multimap extraHeaders, - Multimap extraQueryParams) - throws InsufficientDataException, InternalException, InvalidKeyException, IOException, - NoSuchAlgorithmException, XmlParserException { - Multimap queryParams = - merge( - extraQueryParams, - getCommonListObjectsQueryParams(delimiter, encodingType, maxKeys, prefix)); - if (marker != null) queryParams.put("marker", marker); - - return getRegionAsync(bucketName, region) - .thenCompose( - location -> { - try { - return executeAsync( - Method.GET, - bucketName, - null, - location, - httpHeaders(extraHeaders), - queryParams, - null, - 0); - } catch (InsufficientDataException - | InternalException - | InvalidKeyException - | IOException - | NoSuchAlgorithmException - | XmlParserException e) { - throw new CompletionException(e); - } - }) - .thenApply( - response -> { - try { - ListBucketResultV1 result = - Xml.unmarshal(ListBucketResultV1.class, response.body().charStream()); - return new ListObjectsV1Response(response.headers(), bucketName, region, result); - } catch (XmlParserException e) { - throw new CompletionException(e); - } finally { - response.close(); - } - }); - } - - /** - * Do ListObjects - * version 1 S3 API. - * - * @param bucketName Name of the bucket. - * @param region Region of the bucket (Optional). - * @param delimiter Delimiter (Optional). - * @param encodingType Encoding type (Optional). - * @param marker Marker (Optional). - * @param maxKeys Maximum object information to fetch (Optional). - * @param prefix Prefix (Optional). - * @param extraHeaders Extra headers for request (Optional). - * @param extraQueryParams Extra query parameters for request (Optional). - * @return {@link ListObjectsV1Response} 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. - * @deprecated This method is no longer supported. Use {@link #listObjectsV1Async}. - */ - @Deprecated - protected ListObjectsV1Response listObjectsV1( - String bucketName, - String region, - String delimiter, - String encodingType, - String marker, - Integer maxKeys, - String prefix, - Multimap extraHeaders, - Multimap extraQueryParams) - throws NoSuchAlgorithmException, InsufficientDataException, IOException, InvalidKeyException, - ServerException, XmlParserException, ErrorResponseException, InternalException, - InvalidResponseException { - try { - return listObjectsV1Async( - bucketName, - region, - delimiter, - encodingType, - marker, - maxKeys, - prefix, - extraHeaders, - extraQueryParams) - .get(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - throwEncapsulatedException(e); - return null; - } - } - - /** - * Do ListObjectVersions - * API asynchronously. - * - * @param bucketName Name of the bucket. - * @param region Region of the bucket (Optional). - * @param delimiter Delimiter (Optional). - * @param encodingType Encoding type (Optional). - * @param keyMarker Key marker (Optional). - * @param maxKeys Maximum object information to fetch (Optional). - * @param prefix Prefix (Optional). - * @param versionIdMarker Version ID marker (Optional). - * @param extraHeaders Extra headers for request (Optional). - * @param extraQueryParams Extra query parameters for request (Optional). - * @return {@link CompletableFuture}<{@link ListObjectVersionsResponse}> 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. - */ - protected CompletableFuture listObjectVersionsAsync( - String bucketName, - String region, - String delimiter, - String encodingType, - String keyMarker, - Integer maxKeys, - String prefix, - String versionIdMarker, - Multimap extraHeaders, - Multimap extraQueryParams) - throws InsufficientDataException, InternalException, InvalidKeyException, IOException, - NoSuchAlgorithmException, XmlParserException { - Multimap queryParams = - merge( - extraQueryParams, - getCommonListObjectsQueryParams(delimiter, encodingType, maxKeys, prefix)); - if (keyMarker != null) queryParams.put("key-marker", keyMarker); - if (versionIdMarker != null) queryParams.put("version-id-marker", versionIdMarker); - queryParams.put("versions", ""); - - return getRegionAsync(bucketName, region) - .thenCompose( - location -> { - try { - return executeAsync( - Method.GET, - bucketName, - null, - location, - httpHeaders(extraHeaders), - queryParams, - null, - 0); - } catch (InsufficientDataException - | InternalException - | InvalidKeyException - | IOException - | NoSuchAlgorithmException - | XmlParserException e) { - throw new CompletionException(e); - } - }) - .thenApply( - response -> { - try { - ListVersionsResult result = - Xml.unmarshal(ListVersionsResult.class, response.body().charStream()); - return new ListObjectVersionsResponse( - response.headers(), bucketName, region, result); - } catch (XmlParserException e) { - throw new CompletionException(e); - } finally { - response.close(); - } - }); - } - - /** - * Do ListObjectVersions - * API. - * - * @param bucketName Name of the bucket. - * @param region Region of the bucket (Optional). - * @param delimiter Delimiter (Optional). - * @param encodingType Encoding type (Optional). - * @param keyMarker Key marker (Optional). - * @param maxKeys Maximum object information to fetch (Optional). - * @param prefix Prefix (Optional). - * @param versionIdMarker Version ID marker (Optional). - * @param extraHeaders Extra headers for request (Optional). - * @param extraQueryParams Extra query parameters for request (Optional). - * @return {@link ListObjectVersionsResponse} 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. - * @deprecated This method is no longer supported. Use {@link #listObjectVersionsAsync}. - */ - @Deprecated - protected ListObjectVersionsResponse listObjectVersions( - String bucketName, - String region, - String delimiter, - String encodingType, - String keyMarker, - Integer maxKeys, - String prefix, - String versionIdMarker, - Multimap extraHeaders, - Multimap extraQueryParams) - throws NoSuchAlgorithmException, InsufficientDataException, IOException, InvalidKeyException, - ServerException, XmlParserException, ErrorResponseException, InternalException, - InvalidResponseException { - try { - return listObjectVersionsAsync( - bucketName, - region, - delimiter, - encodingType, - keyMarker, - maxKeys, - prefix, - versionIdMarker, - extraHeaders, - extraQueryParams) - .get(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - throwEncapsulatedException(e); - return null; - } - } - - private Part[] uploadParts( - PutObjectBaseArgs args, String uploadId, PartReader partReader, PartSource firstPartSource) - throws InterruptedException, ExecutionException, InsufficientDataException, InternalException, - InvalidKeyException, IOException, NoSuchAlgorithmException, XmlParserException { - Part[] parts = new Part[ObjectWriteArgs.MAX_MULTIPART_COUNT]; - int partNumber = 0; - PartSource partSource = firstPartSource; - while (true) { - partNumber++; - - Multimap ssecHeaders = null; - // set encryption headers in the case of SSE-C. - if (args.sse() != null && args.sse() instanceof ServerSideEncryptionCustomerKey) { - ssecHeaders = Multimaps.forMap(args.sse().headers()); - } - - UploadPartResponse response = - uploadPartAsync( - args.bucket(), - args.region(), - args.object(), - partSource, - partNumber, - uploadId, - ssecHeaders, - null) - .get(); - parts[partNumber - 1] = new Part(partNumber, response.etag()); - - partSource = partReader.getPart(); - if (partSource == null) break; - } - - return parts; - } - - private CompletableFuture putMultipartObjectAsync( - PutObjectBaseArgs args, - Multimap headers, - PartReader partReader, - PartSource firstPartSource) - throws InsufficientDataException, InternalException, InvalidKeyException, IOException, - NoSuchAlgorithmException, XmlParserException { - return CompletableFuture.supplyAsync( - () -> { - String uploadId = null; - ObjectWriteResponse response = null; - try { - CreateMultipartUploadResponse createMultipartUploadResponse = - createMultipartUploadAsync( - args.bucket(), - args.region(), - args.object(), - headers, - args.extraQueryParams()) - .get(); - uploadId = createMultipartUploadResponse.result().uploadId(); - Part[] parts = uploadParts(args, uploadId, partReader, firstPartSource); - response = - completeMultipartUploadAsync( - args.bucket(), args.region(), args.object(), uploadId, parts, null, null) - .get(); - } catch (InsufficientDataException - | InternalException - | InvalidKeyException - | IOException - | NoSuchAlgorithmException - | XmlParserException - | InterruptedException - | ExecutionException e) { - Throwable throwable = e; - if (throwable instanceof ExecutionException) { - throwable = ((ExecutionException) throwable).getCause(); - } - if (throwable instanceof CompletionException) { - throwable = ((CompletionException) throwable).getCause(); - } - if (uploadId == null) { - throw new CompletionException(throwable); - } - try { - abortMultipartUploadAsync( - args.bucket(), args.region(), args.object(), uploadId, null, null) - .get(); - } catch (InsufficientDataException - | InternalException - | InvalidKeyException - | IOException - | NoSuchAlgorithmException - | XmlParserException - | InterruptedException - | ExecutionException ex) { - throwable = ex; - if (throwable instanceof ExecutionException) { - throwable = ((ExecutionException) throwable).getCause(); - } - if (throwable instanceof CompletionException) { - throwable = ((CompletionException) throwable).getCause(); - } - } - throw new CompletionException(throwable); - } - return response; - }); - } - - /** - * Execute put object asynchronously from object data from {@link RandomAccessFile} or {@link - * InputStream}. - * - * @param args {@link PutObjectBaseArgs}. - * @param data {@link RandomAccessFile} or {@link InputStream}. - * @param objectSize object size. - * @param partSize part size for multipart upload. - * @param partCount Number of parts for multipart upload. - * @param contentType content-type of object. - * @return {@link CompletableFuture}<{@link ObjectWriteResponse}> object. - * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. - * @throws InternalException thrown to indicate internal library error. - * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. - * @throws IOException thrown to indicate I/O error on S3 operation. - * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. - * @throws XmlParserException thrown to indicate XML parsing error. - */ - protected CompletableFuture putObjectAsync( - PutObjectBaseArgs args, - Object data, - long objectSize, - long partSize, - int partCount, - String contentType) - throws InsufficientDataException, InternalException, InvalidKeyException, IOException, - NoSuchAlgorithmException, XmlParserException { - PartReader partReader = newPartReader(data, objectSize, partSize, partCount); - if (partReader == null) { - throw new IllegalArgumentException("data must be RandomAccessFile or InputStream"); - } - - Multimap headers = newMultimap(args.extraHeaders()); - headers.putAll(args.genHeaders()); - if (!headers.containsKey("Content-Type")) headers.put("Content-Type", contentType); - - return CompletableFuture.supplyAsync( - () -> { - try { - return partReader.getPart(); - } catch (NoSuchAlgorithmException | IOException e) { - throw new CompletionException(e); - } - }) - .thenCompose( - partSource -> { - try { - if (partReader.partCount() == 1) { - return putObjectAsync( - args.bucket(), - args.region(), - args.object(), - partSource, - headers, - args.extraQueryParams()); - } else { - return putMultipartObjectAsync(args, headers, partReader, partSource); - } - } catch (InsufficientDataException - | InternalException - | InvalidKeyException - | IOException - | NoSuchAlgorithmException - | XmlParserException e) { - throw new CompletionException(e); - } - }); - } - - /** - * Do PutObject S3 - * API for {@link PartSource} asynchronously. - * - * @param bucketName Name of the bucket. - * @param region Region of the bucket (Optional). - * @param objectName Object name in the bucket. - * @param partSource PartSource object. - * @param headers Additional headers. - * @param extraQueryParams Additional query parameters if any. - * @return {@link CompletableFuture}<{@link ObjectWriteResponse}> object. - * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. - * @throws InternalException thrown to indicate internal library error. - * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. - * @throws IOException thrown to indicate I/O error on S3 operation. - * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. - * @throws XmlParserException thrown to indicate XML parsing error. - */ - private CompletableFuture putObjectAsync( - String bucketName, - String region, - String objectName, - PartSource partSource, - Multimap headers, - Multimap extraQueryParams) - throws InsufficientDataException, InternalException, InvalidKeyException, IOException, - NoSuchAlgorithmException, XmlParserException { - return getRegionAsync(bucketName, region) - .thenCompose( - location -> { - try { - return executeAsync( - Method.PUT, - bucketName, - objectName, - location, - httpHeaders(headers), - extraQueryParams, - partSource, - 0); - } catch (InsufficientDataException - | InternalException - | InvalidKeyException - | IOException - | NoSuchAlgorithmException - | XmlParserException e) { - throw new CompletionException(e); - } - }) - .thenApply( - response -> { - try { - return new ObjectWriteResponse( - response.headers(), - bucketName, - region, - objectName, - response.header("ETag").replaceAll("\"", ""), - response.header("x-amz-version-id")); - } finally { - response.close(); - } - }); - } - - /** - * Do PutObject S3 - * API asynchronously. - * - * @param bucketName Name of the bucket. - * @param objectName Object name in the bucket. - * @param data Object data must be InputStream, RandomAccessFile, byte[] or String. - * @param length Length of object data. - * @param headers Additional headers. - * @param extraQueryParams Additional query parameters if any. - * @return {@link CompletableFuture}<{@link ObjectWriteResponse}> object. - * @throws InsufficientDataException thrown to indicate not enough data available in InputStream. - * @throws InternalException thrown to indicate internal library error. - * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library. - * @throws IOException thrown to indicate I/O error on S3 operation. - * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library. - * @throws XmlParserException thrown to indicate XML parsing error. - */ - protected CompletableFuture putObjectAsync( - String bucketName, - String region, - String objectName, - Object data, - long length, - Multimap headers, - Multimap extraQueryParams) - throws InsufficientDataException, InternalException, InvalidKeyException, IOException, - NoSuchAlgorithmException, XmlParserException { - if (!(data instanceof InputStream - || data instanceof RandomAccessFile - || data instanceof byte[] - || data instanceof CharSequence)) { - throw new IllegalArgumentException( - "data must be InputStream, RandomAccessFile, byte[] or String"); - } - - PartReader partReader = newPartReader(data, length, length, 1); - - if (partReader != null) { - return putObjectAsync( - bucketName, region, objectName, partReader.getPart(), headers, extraQueryParams); - } - - return getRegionAsync(bucketName, region) - .thenCompose( - location -> { - try { - return executeAsync( - Method.PUT, - bucketName, - objectName, - location, - httpHeaders(headers), - extraQueryParams, - data, - (int) length); - } catch (InsufficientDataException - | InternalException - | InvalidKeyException - | IOException - | NoSuchAlgorithmException - | XmlParserException e) { - throw new CompletionException(e); - } - }) - .thenApply( - response -> { - try { - return new ObjectWriteResponse( - response.headers(), - bucketName, - region, - objectName, - response.header("ETag").replaceAll("\"", ""), - response.header("x-amz-version-id")); - } finally { - response.close(); - } - }); - } - - /** - * Do PutObject S3 - * API. - * - * @param bucketName Name of the bucket. - * @param objectName Object name in the bucket. - * @param data Object data must be InputStream, RandomAccessFile, byte[] or String. - * @param length Length of object data. - * @param headers Additional headers. - * @param extraQueryParams Additional query parameters if any. - * @return {@link ObjectWriteResponse} 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. - * @deprecated This method is no longer supported. Use {@link #putObjectAsync}. - */ - @Deprecated - protected ObjectWriteResponse putObject( - String bucketName, - String region, - String objectName, - Object data, - long length, - Multimap headers, - Multimap extraQueryParams) - throws NoSuchAlgorithmException, InsufficientDataException, IOException, InvalidKeyException, - ServerException, XmlParserException, ErrorResponseException, InternalException, - InvalidResponseException { - try { - return putObjectAsync(bucketName, region, objectName, data, length, headers, extraQueryParams) - .get(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - throwEncapsulatedException(e); - return null; - } - } - - /** - * Do ListMultipartUploads - * S3 API asynchronously. - * - * @param bucketName Name of the bucket. - * @param region Region of the bucket (Optional). - * @param delimiter Delimiter (Optional). - * @param encodingType Encoding type (Optional). - * @param keyMarker Key marker (Optional). - * @param maxUploads Maximum upload information to fetch (Optional). - * @param prefix Prefix (Optional). - * @param uploadIdMarker Upload ID marker (Optional). - * @param extraHeaders Extra headers for request (Optional). - * @param extraQueryParams Extra query parameters for request (Optional). - * @return {@link CompletableFuture}<{@link ListMultipartUploadsResponse}> 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 listMultipartUploadsAsync( - String bucketName, - String region, - String delimiter, - String encodingType, - String keyMarker, - Integer maxUploads, - String prefix, - String uploadIdMarker, - Multimap extraHeaders, - Multimap extraQueryParams) - throws InsufficientDataException, InternalException, InvalidKeyException, IOException, - NoSuchAlgorithmException, XmlParserException { - Multimap queryParams = - merge( - extraQueryParams, - newMultimap( - "uploads", - "", - "delimiter", - (delimiter != null) ? delimiter : "", - "max-uploads", - (maxUploads != null) ? maxUploads.toString() : "1000", - "prefix", - (prefix != null) ? prefix : "")); - if (encodingType != null) queryParams.put("encoding-type", encodingType); - if (keyMarker != null) queryParams.put("key-marker", keyMarker); - if (uploadIdMarker != null) queryParams.put("upload-id-marker", uploadIdMarker); - - return getRegionAsync(bucketName, region) - .thenCompose( - location -> { - try { - return executeAsync( - Method.GET, - bucketName, - null, - location, - httpHeaders(extraHeaders), - queryParams, - null, - 0); - } catch (InsufficientDataException - | InternalException - | InvalidKeyException - | IOException - | NoSuchAlgorithmException - | XmlParserException e) { - throw new CompletionException(e); - } - }) - .thenApply( - response -> { - try { - ListMultipartUploadsResult result = - Xml.unmarshal(ListMultipartUploadsResult.class, response.body().charStream()); - return new ListMultipartUploadsResponse( - response.headers(), bucketName, region, result); - } catch (XmlParserException e) { - throw new CompletionException(e); - } finally { - response.close(); - } - }); - } - - /** - * Do ListMultipartUploads - * S3 API. - * - * @param bucketName Name of the bucket. - * @param region Region of the bucket (Optional). - * @param delimiter Delimiter (Optional). - * @param encodingType Encoding type (Optional). - * @param keyMarker Key marker (Optional). - * @param maxUploads Maximum upload information to fetch (Optional). - * @param prefix Prefix (Optional). - * @param uploadIdMarker Upload ID marker (Optional). - * @param extraHeaders Extra headers for request (Optional). - * @param extraQueryParams Extra query parameters for request (Optional). - * @return {@link ListMultipartUploadsResponse} 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. - * @deprecated This method is no longer supported. Use {@link #listMultipartUploadsAsync}. - */ - @Deprecated - protected ListMultipartUploadsResponse listMultipartUploads( - String bucketName, - String region, - String delimiter, - String encodingType, - String keyMarker, - Integer maxUploads, - String prefix, - String uploadIdMarker, - Multimap extraHeaders, - Multimap extraQueryParams) - throws NoSuchAlgorithmException, InsufficientDataException, IOException, InvalidKeyException, - ServerException, XmlParserException, ErrorResponseException, InternalException, - InvalidResponseException { - try { - return listMultipartUploadsAsync( - bucketName, - region, - delimiter, - encodingType, - keyMarker, - maxUploads, - prefix, - uploadIdMarker, - extraHeaders, - extraQueryParams) - .get(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - throwEncapsulatedException(e); - return null; - } - } - - /** - * Do ListParts S3 - * API asynchronously. - * - * @param bucketName Name of the bucket. - * @param region Name of the bucket (Optional). - * @param objectName Object name in the bucket. - * @param maxParts Maximum parts information to fetch (Optional). - * @param partNumberMarker Part number marker (Optional). - * @param uploadId Upload ID. - * @param extraHeaders Extra headers for request (Optional). - * @param extraQueryParams Extra query parameters for request (Optional). - * @return {@link CompletableFuture}<{@link ListPartsResponse}> 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 listPartsAsync( - String bucketName, - String region, - String objectName, - Integer maxParts, - Integer partNumberMarker, - String uploadId, - Multimap extraHeaders, - Multimap extraQueryParams) - throws InsufficientDataException, InternalException, InvalidKeyException, IOException, - NoSuchAlgorithmException, XmlParserException { - Multimap queryParams = - merge( - extraQueryParams, - newMultimap( - UPLOAD_ID, - uploadId, - "max-parts", - (maxParts != null) ? maxParts.toString() : "1000")); - if (partNumberMarker != null) { - queryParams.put("part-number-marker", partNumberMarker.toString()); - } - - return getRegionAsync(bucketName, region) - .thenCompose( - location -> { - try { - return executeAsync( - Method.GET, - bucketName, - objectName, - location, - httpHeaders(extraHeaders), - queryParams, - null, - 0); - } catch (InsufficientDataException - | InternalException - | InvalidKeyException - | IOException - | NoSuchAlgorithmException - | XmlParserException e) { - throw new CompletionException(e); - } - }) - .thenApply( - response -> { - try { - ListPartsResult result = - Xml.unmarshal(ListPartsResult.class, response.body().charStream()); - return new ListPartsResponse( - response.headers(), bucketName, region, objectName, result); - } catch (XmlParserException e) { - throw new CompletionException(e); - } finally { - response.close(); - } - }); - } - - /** - * Do ListParts S3 - * API. - * - * @param bucketName Name of the bucket. - * @param region Name of the bucket (Optional). - * @param objectName Object name in the bucket. - * @param maxParts Maximum parts information to fetch (Optional). - * @param partNumberMarker Part number marker (Optional). - * @param uploadId Upload ID. - * @param extraHeaders Extra headers for request (Optional). - * @param extraQueryParams Extra query parameters for request (Optional). - * @return {@link ListPartsResponse} 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. - * @deprecated This method is no longer supported. Use {@link #listPartsAsync}. - */ - @Deprecated - protected ListPartsResponse listParts( - String bucketName, - String region, - String objectName, - Integer maxParts, - Integer partNumberMarker, - String uploadId, - Multimap extraHeaders, - Multimap extraQueryParams) - throws NoSuchAlgorithmException, InsufficientDataException, IOException, InvalidKeyException, - ServerException, XmlParserException, ErrorResponseException, InternalException, - InvalidResponseException { - try { - return listPartsAsync( - bucketName, - region, - objectName, - maxParts, - partNumberMarker, - uploadId, - extraHeaders, - extraQueryParams) - .get(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - throwEncapsulatedException(e); - return null; - } - } - - /** - * Do UploadPart S3 - * API for PartSource asynchronously. - * - * @param bucketName Name of the bucket. - * @param region Region of the bucket (Optional). - * @param objectName Object name in the bucket. - * @param partSource PartSource Object. - * @param partNumber Part number. - * @param uploadId Upload ID. - * @param extraHeaders Extra headers for request (Optional). - * @param extraQueryParams Extra query parameters for request (Optional). - * @return {@link CompletableFuture}<{@link UploadPartResponse}> 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 uploadPartAsync( - String bucketName, - String region, - String objectName, - PartSource partSource, - int partNumber, - String uploadId, - Multimap extraHeaders, - Multimap extraQueryParams) - throws InsufficientDataException, InternalException, InvalidKeyException, IOException, - NoSuchAlgorithmException, XmlParserException { - return getRegionAsync(bucketName, region) - .thenCompose( - location -> { - try { - return executeAsync( - Method.PUT, - bucketName, - objectName, - location, - httpHeaders(extraHeaders), - merge( - extraQueryParams, - newMultimap( - "partNumber", Integer.toString(partNumber), UPLOAD_ID, uploadId)), - partSource, - 0); - } catch (InsufficientDataException - | InternalException - | InvalidKeyException - | IOException - | NoSuchAlgorithmException - | XmlParserException e) { - throw new CompletionException(e); - } - }) - .thenApply( - response -> { - try { - return new UploadPartResponse( - response.headers(), - bucketName, - region, - objectName, - uploadId, - partNumber, - response.header("ETag").replaceAll("\"", "")); - } finally { - response.close(); - } - }); - } - - /** - * Do UploadPart S3 - * API asynchronously. - * - * @param bucketName Name of the bucket. - * @param region Region of the bucket (Optional). - * @param objectName Object name in the bucket. - * @param data Object data must be InputStream, RandomAccessFile, byte[] or String. - * @param length Length of object data. - * @param uploadId Upload ID. - * @param partNumber Part number. - * @param extraHeaders Extra headers for request (Optional). - * @param extraQueryParams Extra query parameters for request (Optional). - * @return {@link CompletableFuture}<{@link UploadPartResponse}> 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 uploadPartAsync( - String bucketName, - String region, - String objectName, - Object data, - long length, - String uploadId, - int partNumber, - Multimap extraHeaders, - Multimap extraQueryParams) - throws InsufficientDataException, InternalException, InvalidKeyException, IOException, - NoSuchAlgorithmException, XmlParserException { - if (!(data instanceof InputStream - || data instanceof RandomAccessFile - || data instanceof byte[] - || data instanceof CharSequence)) { - throw new IllegalArgumentException( - "data must be InputStream, RandomAccessFile, byte[] or String"); - } - - PartReader partReader = newPartReader(data, length, length, 1); - - if (partReader != null) { - return uploadPartAsync( - bucketName, - region, - objectName, - partReader.getPart(), - partNumber, - uploadId, - extraHeaders, - extraQueryParams); - } - - return getRegionAsync(bucketName, region) - .thenCompose( - location -> { - try { - return executeAsync( - Method.PUT, - bucketName, - objectName, - location, - httpHeaders(extraHeaders), - merge( - extraQueryParams, - newMultimap( - "partNumber", Integer.toString(partNumber), UPLOAD_ID, uploadId)), - data, - (int) length); - } catch (InsufficientDataException - | InternalException - | InvalidKeyException - | IOException - | NoSuchAlgorithmException - | XmlParserException e) { - throw new CompletionException(e); - } - }) - .thenApply( - response -> { - try { - return new UploadPartResponse( - response.headers(), - bucketName, - region, - objectName, - uploadId, - partNumber, - response.header("ETag").replaceAll("\"", "")); - } finally { - response.close(); - } - }); - } - - /** - * Do UploadPart S3 - * API. - * - * @param bucketName Name of the bucket. - * @param region Region of the bucket (Optional). - * @param objectName Object name in the bucket. - * @param data Object data must be InputStream, RandomAccessFile, byte[] or String. - * @param length Length of object data. - * @param uploadId Upload ID. - * @param partNumber Part number. - * @param extraHeaders Extra headers for request (Optional). - * @param extraQueryParams Extra query parameters for request (Optional). - * @return {@link UploadPartResponse} 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. - * @deprecated This method is no longer supported. Use {@link #uploadPartAsync}. - */ - @Deprecated - protected UploadPartResponse uploadPart( - String bucketName, - String region, - String objectName, - Object data, - long length, - String uploadId, - int partNumber, - Multimap extraHeaders, - Multimap extraQueryParams) - throws NoSuchAlgorithmException, InsufficientDataException, IOException, InvalidKeyException, - ServerException, XmlParserException, ErrorResponseException, InternalException, - InvalidResponseException { - try { - return uploadPartAsync( - bucketName, - region, - objectName, - data, - length, - uploadId, - partNumber, - extraHeaders, - extraQueryParams) - .get(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - throwEncapsulatedException(e); - return null; - } - } - - /** - * Do UploadPartCopy - * S3 API. - * - * @param bucketName Name of the bucket. - * @param region Region of the bucket (Optional). - * @param objectName Object name in the bucket. - * @param uploadId Upload ID. - * @param partNumber Part number. - * @param headers Request headers with source object definitions. - * @param extraQueryParams Extra query parameters for request (Optional). - * @return {@link UploadPartCopyResponse} 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. - * @deprecated This method is no longer supported. Use {@link #uploadPartCopyAsync}. - */ - @Deprecated - protected UploadPartCopyResponse uploadPartCopy( - String bucketName, - String region, - String objectName, - String uploadId, - int partNumber, - Multimap headers, - Multimap extraQueryParams) - throws NoSuchAlgorithmException, InsufficientDataException, IOException, InvalidKeyException, - ServerException, XmlParserException, ErrorResponseException, InternalException, - InvalidResponseException { - try { - return uploadPartCopyAsync( - bucketName, region, objectName, uploadId, partNumber, headers, extraQueryParams) - .get(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - throwEncapsulatedException(e); - return null; - } - } - - /** - * Do UploadPartCopy - * S3 API. - * - * @param bucketName Name of the bucket. - * @param region Region of the bucket (Optional). - * @param objectName Object name in the bucket. - * @param uploadId Upload ID. - * @param partNumber Part number. - * @param headers Request headers with source object definitions. - * @param extraQueryParams Extra query parameters for request (Optional). - * @return {@link CompletableFuture}<{@link UploadPartCopyResponse}> 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 uploadPartCopyAsync( - String bucketName, - String region, - String objectName, - String uploadId, - int partNumber, - Multimap headers, - Multimap extraQueryParams) - throws InsufficientDataException, InternalException, InvalidKeyException, IOException, - NoSuchAlgorithmException, XmlParserException { - return getRegionAsync(bucketName, region) - .thenCompose( - location -> { - try { - return executeAsync( - Method.PUT, - bucketName, - objectName, - location, - httpHeaders(headers), - merge( - extraQueryParams, - newMultimap( - "partNumber", Integer.toString(partNumber), "uploadId", uploadId)), - null, - 0); - } catch (InsufficientDataException - | InternalException - | InvalidKeyException - | IOException - | NoSuchAlgorithmException - | XmlParserException e) { - throw new CompletionException(e); - } - }) - .thenApply( - response -> { - try { - CopyPartResult result = - Xml.unmarshal(CopyPartResult.class, response.body().charStream()); - return new UploadPartCopyResponse( - response.headers(), - bucketName, - region, - objectName, - uploadId, - partNumber, - result); - } catch (XmlParserException e) { - throw new CompletionException(e); - } finally { - response.close(); - } - }); - } - - /** - * Do ListBuckets - * S3 API. - * - * @param bucketRegion Fetch buckets from the region (Optional). - * @param maxBuckets Maximum buckets to be fetched (Optional). - * @param prefix Bucket name prefix (Optional). - * @param continuationToken continuation token (Optional). - * @param extraHeaders Extra headers for request (Optional). - * @param extraQueryParams Extra query parameters for request (Optional). - * @return {@link CompletableFuture}<{@link ListBucketsResponse}> 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. - */ - protected CompletableFuture listBucketsAsync( - String bucketRegion, - Integer maxBuckets, - String prefix, - String continuationToken, - Multimap extraHeaders, - Multimap extraQueryParams) - throws InsufficientDataException, InternalException, InvalidKeyException, IOException, - NoSuchAlgorithmException, XmlParserException { - Multimap queryParams = newMultimap(extraQueryParams); - if (bucketRegion != null) queryParams.put("bucket-region", bucketRegion); - if (maxBuckets != null) - queryParams.put("max-buckets", Integer.toString(maxBuckets > 0 ? maxBuckets : 10000)); - if (prefix != null) queryParams.put("prefix", prefix); - if (continuationToken != null) queryParams.put("continuation-token", continuationToken); - return executeGetAsync(null, extraHeaders, queryParams) - .thenApply( - response -> { - try { - ListAllMyBucketsResult result = - Xml.unmarshal(ListAllMyBucketsResult.class, response.body().charStream()); - return new ListBucketsResponse(response.headers(), result); - } catch (XmlParserException e) { - throw new CompletionException(e); - } finally { - response.close(); - } - }); - } - - @Override - public void close() throws Exception { - if (closeHttpClient) { - httpClient.dispatcher().executorService().shutdown(); - httpClient.connectionPool().evictAll(); - } - } -} diff --git a/api/src/main/java/io/minio/S3Escaper.java b/api/src/main/java/io/minio/S3Escaper.java deleted file mode 100644 index 028dd83b1..000000000 --- a/api/src/main/java/io/minio/S3Escaper.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2016 MinIO, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.minio; - -import com.google.common.escape.Escaper; -import com.google.common.net.UrlEscapers; - -public class S3Escaper { - private static final Escaper ESCAPER = UrlEscapers.urlPathSegmentEscaper(); - - /** Returns S3 encoded string. */ - public static String encode(String str) { - if (str == null) { - return ""; - } - - StringBuilder builder = new StringBuilder(); - for (char ch : ESCAPER.escape(str).toCharArray()) { - switch (ch) { - case '!': - builder.append("%21"); - break; - case '$': - builder.append("%24"); - break; - case '&': - builder.append("%26"); - break; - case '\'': - builder.append("%27"); - break; - case '(': - builder.append("%28"); - break; - case ')': - builder.append("%29"); - break; - case '*': - builder.append("%2A"); - break; - case '+': - builder.append("%2B"); - break; - case ',': - builder.append("%2C"); - break; - case '/': - builder.append("%2F"); - break; - case ':': - builder.append("%3A"); - break; - case ';': - builder.append("%3B"); - break; - case '=': - builder.append("%3D"); - break; - case '@': - builder.append("%40"); - break; - case '[': - builder.append("%5B"); - break; - case ']': - builder.append("%5D"); - break; - default: - builder.append(ch); - } - } - return builder.toString(); - } - - /** Returns S3 encoded string of given path where multiple '/' are trimmed. */ - public static String encodePath(String path) { - final StringBuilder encodedPath = new StringBuilder(); - for (String pathSegment : path.split("/")) { - if (!pathSegment.isEmpty()) { - if (encodedPath.length() > 0) { - encodedPath.append("/"); - } - encodedPath.append(S3Escaper.encode(pathSegment)); - } - } - - if (path.startsWith("/")) { - encodedPath.insert(0, "/"); - } - if (path.endsWith("/")) { - encodedPath.append("/"); - } - - return encodedPath.toString(); - } -} diff --git a/api/src/main/java/io/minio/SelectObjectContentArgs.java b/api/src/main/java/io/minio/SelectObjectContentArgs.java index 684f429a4..b756d8811 100644 --- a/api/src/main/java/io/minio/SelectObjectContentArgs.java +++ b/api/src/main/java/io/minio/SelectObjectContentArgs.java @@ -65,7 +65,7 @@ public static Builder builder() { public static final class Builder extends ObjectReadArgs.Builder { private void validateSqlExpression(String se) { - validateNotEmptyString(se, "sqlExpression"); + Utils.validateNotEmptyString(se, "sqlExpression"); } public Builder sqlExpression(String sqlExpression) { @@ -75,7 +75,7 @@ public Builder sqlExpression(String sqlExpression) { } private void validateInputSerialization(InputSerialization is) { - validateNotNull(is, "inputSerialization"); + Utils.validateNotNull(is, "inputSerialization"); } public Builder inputSerialization(InputSerialization inputSerialization) { @@ -85,7 +85,7 @@ public Builder inputSerialization(InputSerialization inputSerialization) { } private void validateOutputSerialization(OutputSerialization os) { - validateNotNull(os, "outputSerialization"); + Utils.validateNotNull(os, "outputSerialization"); } public Builder outputSerialization(OutputSerialization outputSerialization) { diff --git a/api/src/main/java/io/minio/ServerSideEncryption.java b/api/src/main/java/io/minio/ServerSideEncryption.java index c4e90cce0..28befbd01 100644 --- a/api/src/main/java/io/minio/ServerSideEncryption.java +++ b/api/src/main/java/io/minio/ServerSideEncryption.java @@ -16,9 +16,20 @@ package io.minio; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.io.BaseEncoding; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; +import java.util.HashMap; import java.util.Map; +import javax.crypto.SecretKey; +import javax.security.auth.DestroyFailedException; -/** Base class of server-side encryption. */ +/** Server-side encryption support. */ public abstract class ServerSideEncryption { private static final Map emptyHeaders = Utils.unmodifiableMap(null); @@ -31,4 +42,131 @@ public boolean tlsRequired() { public Map copySourceHeaders() { return emptyHeaders; } + + /** S3 type of Server-side encryption. */ + public static class S3 extends ServerSideEncryption { + private static final Map headers; + + static { + Map map = new HashMap<>(); + map.put("X-Amz-Server-Side-Encryption", "AES256"); + headers = Utils.unmodifiableMap(map); + } + + @Override + public final Map headers() { + return headers; + } + + @Override + public final boolean tlsRequired() { + return false; + } + + @Override + public String toString() { + return "SSE-S3"; + } + } + + /** KMS type of Server-side encryption. */ + public static class KMS extends ServerSideEncryption { + private static final ObjectMapper objectMapper = new ObjectMapper(); + private final Map headers; + + public KMS(String keyId, Map context) throws JsonProcessingException { + if (keyId == null) { + throw new IllegalArgumentException("Key ID cannot be null"); + } + + Map headers = new HashMap<>(); + headers.put("X-Amz-Server-Side-Encryption", "aws:kms"); + headers.put("X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id", keyId); + if (context != null) { + headers.put( + "X-Amz-Server-Side-Encryption-Context", + Base64.getEncoder() + .encodeToString( + objectMapper.writeValueAsString(context).getBytes(StandardCharsets.UTF_8))); + } + + this.headers = Utils.unmodifiableMap(headers); + } + + @Override + public final Map headers() { + return headers; + } + + @Override + public String toString() { + return "SSE-KMS"; + } + } + + /** Customer-key type of Server-side encryption. */ + public static class CustomerKey extends ServerSideEncryption { + private boolean isDestroyed = false; + private final SecretKey secretKey; + private final Map headers; + private final Map copySourceHeaders; + + public CustomerKey(SecretKey key) throws InvalidKeyException, NoSuchAlgorithmException { + if (key == null || !key.getAlgorithm().equals("AES") || key.getEncoded().length != 32) { + throw new IllegalArgumentException("Secret key must be 256 bit AES key"); + } + + if (key.isDestroyed()) { + throw new IllegalArgumentException("Secret key already destroyed"); + } + + this.secretKey = key; + + byte[] keyBytes = key.getEncoded(); + MessageDigest md5 = MessageDigest.getInstance("MD5"); + md5.update(keyBytes); + String customerKey = BaseEncoding.base64().encode(keyBytes); + String customerKeyMd5 = BaseEncoding.base64().encode(md5.digest()); + + Map map = new HashMap<>(); + map.put("X-Amz-Server-Side-Encryption-Customer-Algorithm", "AES256"); + map.put("X-Amz-Server-Side-Encryption-Customer-Key", customerKey); + map.put("X-Amz-Server-Side-Encryption-Customer-Key-Md5", customerKeyMd5); + this.headers = Utils.unmodifiableMap(map); + + map = new HashMap<>(); + map.put("X-Amz-Copy-Source-Server-Side-Encryption-Customer-Algorithm", "AES256"); + map.put("X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key", customerKey); + map.put("X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key-Md5", customerKeyMd5); + this.copySourceHeaders = Utils.unmodifiableMap(map); + } + + @Override + public final Map headers() { + if (isDestroyed) { + throw new IllegalStateException("Secret key was destroyed"); + } + + return headers; + } + + @Override + public final Map copySourceHeaders() { + if (isDestroyed) { + throw new IllegalStateException("Secret key was destroyed"); + } + + return copySourceHeaders; + } + + public final void destroy() throws DestroyFailedException { + secretKey.destroy(); + isDestroyed = true; + } + + @Override + public String toString() { + return "SSE-C"; + } + } } diff --git a/api/src/main/java/io/minio/ServerSideEncryptionCustomerKey.java b/api/src/main/java/io/minio/ServerSideEncryptionCustomerKey.java deleted file mode 100644 index 74f883c6d..000000000 --- a/api/src/main/java/io/minio/ServerSideEncryptionCustomerKey.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * 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 com.google.common.io.BaseEncoding; -import java.security.InvalidKeyException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.HashMap; -import java.util.Map; -import javax.crypto.SecretKey; -import javax.security.auth.DestroyFailedException; - -/** Customer-key type of Server-side encryption. */ -public class ServerSideEncryptionCustomerKey extends ServerSideEncryption { - private boolean isDestroyed = false; - private final SecretKey secretKey; - private final Map headers; - private final Map copySourceHeaders; - - public ServerSideEncryptionCustomerKey(SecretKey key) - throws InvalidKeyException, NoSuchAlgorithmException { - if (key == null || !key.getAlgorithm().equals("AES") || key.getEncoded().length != 32) { - throw new IllegalArgumentException("Secret key must be 256 bit AES key"); - } - - if (key.isDestroyed()) { - throw new IllegalArgumentException("Secret key already destroyed"); - } - - this.secretKey = key; - - byte[] keyBytes = key.getEncoded(); - MessageDigest md5 = MessageDigest.getInstance("MD5"); - md5.update(keyBytes); - String customerKey = BaseEncoding.base64().encode(keyBytes); - String customerKeyMd5 = BaseEncoding.base64().encode(md5.digest()); - - Map map = new HashMap<>(); - map.put("X-Amz-Server-Side-Encryption-Customer-Algorithm", "AES256"); - map.put("X-Amz-Server-Side-Encryption-Customer-Key", customerKey); - map.put("X-Amz-Server-Side-Encryption-Customer-Key-Md5", customerKeyMd5); - this.headers = Utils.unmodifiableMap(map); - - map = new HashMap<>(); - map.put("X-Amz-Copy-Source-Server-Side-Encryption-Customer-Algorithm", "AES256"); - map.put("X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key", customerKey); - map.put("X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key-Md5", customerKeyMd5); - this.copySourceHeaders = Utils.unmodifiableMap(map); - } - - @Override - public final Map headers() { - if (isDestroyed) { - throw new IllegalStateException("Secret key was destroyed"); - } - - return headers; - } - - @Override - public final Map copySourceHeaders() { - if (isDestroyed) { - throw new IllegalStateException("Secret key was destroyed"); - } - - return copySourceHeaders; - } - - public final void destroy() throws DestroyFailedException { - secretKey.destroy(); - isDestroyed = true; - } - - @Override - public String toString() { - return "SSE-C"; - } -} diff --git a/api/src/main/java/io/minio/ServerSideEncryptionKms.java b/api/src/main/java/io/minio/ServerSideEncryptionKms.java deleted file mode 100644 index f3c898287..000000000 --- a/api/src/main/java/io/minio/ServerSideEncryptionKms.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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 com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import java.nio.charset.StandardCharsets; -import java.util.Base64; -import java.util.HashMap; -import java.util.Map; - -/** KMS type of Server-side encryption. */ -public class ServerSideEncryptionKms extends ServerSideEncryption { - private static final ObjectMapper objectMapper = new ObjectMapper(); - private final Map headers; - - public ServerSideEncryptionKms(String keyId, Map context) - throws JsonProcessingException { - if (keyId == null) { - throw new IllegalArgumentException("Key ID cannot be null"); - } - - Map headers = new HashMap<>(); - headers.put("X-Amz-Server-Side-Encryption", "aws:kms"); - headers.put("X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id", keyId); - if (context != null) { - headers.put( - "X-Amz-Server-Side-Encryption-Context", - Base64.getEncoder() - .encodeToString( - objectMapper.writeValueAsString(context).getBytes(StandardCharsets.UTF_8))); - } - - this.headers = Utils.unmodifiableMap(headers); - } - - @Override - public final Map headers() { - return headers; - } - - @Override - public String toString() { - return "SSE-KMS"; - } -} diff --git a/api/src/main/java/io/minio/ServerSideEncryptionS3.java b/api/src/main/java/io/minio/ServerSideEncryptionS3.java deleted file mode 100644 index 4a2615ee7..000000000 --- a/api/src/main/java/io/minio/ServerSideEncryptionS3.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 java.util.HashMap; -import java.util.Map; - -/** S3 type of Server-side encryption. */ -public class ServerSideEncryptionS3 extends ServerSideEncryption { - private static final Map headers; - - static { - Map map = new HashMap<>(); - map.put("X-Amz-Server-Side-Encryption", "AES256"); - headers = Utils.unmodifiableMap(map); - } - - @Override - public final Map headers() { - return headers; - } - - @Override - public final boolean tlsRequired() { - return false; - } - - @Override - public String toString() { - return "SSE-S3"; - } -} diff --git a/api/src/main/java/io/minio/SetBucketCorsArgs.java b/api/src/main/java/io/minio/SetBucketCorsArgs.java index 11d6b59ee..7a297d25e 100644 --- a/api/src/main/java/io/minio/SetBucketCorsArgs.java +++ b/api/src/main/java/io/minio/SetBucketCorsArgs.java @@ -36,7 +36,7 @@ public static Builder builder() { /** Argument builder of {@link SetBucketCorsArgs}. */ public static final class Builder extends BucketArgs.Builder { private void validateCors(CORSConfiguration config) { - validateNotNull(config, "CORS configuration"); + Utils.validateNotNull(config, "CORS configuration"); } protected void validate(SetBucketCorsArgs args) { diff --git a/api/src/main/java/io/minio/SetBucketEncryptionArgs.java b/api/src/main/java/io/minio/SetBucketEncryptionArgs.java index 3afa3d2c5..d2284bc61 100644 --- a/api/src/main/java/io/minio/SetBucketEncryptionArgs.java +++ b/api/src/main/java/io/minio/SetBucketEncryptionArgs.java @@ -37,7 +37,7 @@ public static Builder builder() { /** Argument builder of {@link SetBucketEncryptionArgs}. */ public static final class Builder extends BucketArgs.Builder { private void validateConfig(SseConfiguration config) { - validateNotNull(config, "encryption configuration"); + Utils.validateNotNull(config, "encryption configuration"); } protected void validate(SetBucketEncryptionArgs args) { diff --git a/api/src/main/java/io/minio/SetBucketLifecycleArgs.java b/api/src/main/java/io/minio/SetBucketLifecycleArgs.java index ac1894888..a22e89ed9 100644 --- a/api/src/main/java/io/minio/SetBucketLifecycleArgs.java +++ b/api/src/main/java/io/minio/SetBucketLifecycleArgs.java @@ -37,7 +37,7 @@ public static Builder builder() { /** Argument builder of {@link SetBucketLifecycleArgs}. */ public static final class Builder extends BucketArgs.Builder { private void validateConfig(LifecycleConfiguration config) { - validateNotNull(config, "lifecycle configuration"); + Utils.validateNotNull(config, "lifecycle configuration"); } protected void validate(SetBucketLifecycleArgs args) { diff --git a/api/src/main/java/io/minio/SetBucketNotificationArgs.java b/api/src/main/java/io/minio/SetBucketNotificationArgs.java index 189a338a9..80d583801 100644 --- a/api/src/main/java/io/minio/SetBucketNotificationArgs.java +++ b/api/src/main/java/io/minio/SetBucketNotificationArgs.java @@ -37,7 +37,7 @@ public static Builder builder() { /** Argument builder of {@link SetBucketNotificationArgs}. */ public static final class Builder extends BucketArgs.Builder { private void validateConfig(NotificationConfiguration config) { - validateNotNull(config, "notification configuration"); + Utils.validateNotNull(config, "notification configuration"); } protected void validate(SetBucketNotificationArgs args) { diff --git a/api/src/main/java/io/minio/SetBucketPolicyArgs.java b/api/src/main/java/io/minio/SetBucketPolicyArgs.java index c9f454ac1..a02ee7b80 100644 --- a/api/src/main/java/io/minio/SetBucketPolicyArgs.java +++ b/api/src/main/java/io/minio/SetBucketPolicyArgs.java @@ -36,7 +36,7 @@ public static Builder builder() { /** Argument builder of {@link SetBucketPolicyArgs}. */ public static final class Builder extends BucketArgs.Builder { private void validateConfig(String config) { - validateNotNull(config, "policy configuration"); + Utils.validateNotNull(config, "policy configuration"); } @Override diff --git a/api/src/main/java/io/minio/SetBucketReplicationArgs.java b/api/src/main/java/io/minio/SetBucketReplicationArgs.java index 5bafc4f61..eb5cbd133 100644 --- a/api/src/main/java/io/minio/SetBucketReplicationArgs.java +++ b/api/src/main/java/io/minio/SetBucketReplicationArgs.java @@ -42,11 +42,11 @@ public static Builder builder() { /** Argument builder of {@link SetBucketReplicationArgs}. */ public static final class Builder extends BucketArgs.Builder { private void validateConfig(ReplicationConfiguration config) { - validateNotNull(config, "replication configuration"); + Utils.validateNotNull(config, "replication configuration"); } private void validateObjectLockToken(String token) { - validateNullOrNotEmptyString(token, "object lock token"); + Utils.validateNullOrNotEmptyString(token, "object lock token"); } @Override diff --git a/api/src/main/java/io/minio/SetBucketTagsArgs.java b/api/src/main/java/io/minio/SetBucketTagsArgs.java index 3dacccc7c..ce6d54b29 100644 --- a/api/src/main/java/io/minio/SetBucketTagsArgs.java +++ b/api/src/main/java/io/minio/SetBucketTagsArgs.java @@ -37,7 +37,7 @@ public static Builder builder() { /** Argument builder of {@link SetBucketTagsArgs}. */ public static final class Builder extends BucketArgs.Builder { private void validateTags(Tags tags) { - validateNotNull(tags, "tags"); + Utils.validateNotNull(tags, "tags"); } protected void validate(SetBucketTagsArgs args) { @@ -46,7 +46,7 @@ protected void validate(SetBucketTagsArgs args) { } public Builder tags(Map map) { - validateNotNull(map, "map for tags"); + Utils.validateNotNull(map, "map for tags"); operations.add(args -> args.tags = Tags.newBucketTags(map)); return this; } diff --git a/api/src/main/java/io/minio/SetBucketVersioningArgs.java b/api/src/main/java/io/minio/SetBucketVersioningArgs.java index 2f848c994..02beb5a39 100644 --- a/api/src/main/java/io/minio/SetBucketVersioningArgs.java +++ b/api/src/main/java/io/minio/SetBucketVersioningArgs.java @@ -37,7 +37,7 @@ public static Builder builder() { /** Argument builder of {@link SetBucketVersioningArgs}. */ public static final class Builder extends BucketArgs.Builder { private void validateConfig(VersioningConfiguration config) { - validateNotNull(config, "versioning configuration"); + Utils.validateNotNull(config, "versioning configuration"); } protected void validate(SetBucketVersioningArgs args) { diff --git a/api/src/main/java/io/minio/SetObjectLockConfigurationArgs.java b/api/src/main/java/io/minio/SetObjectLockConfigurationArgs.java index 6cef98dff..5fbe5d535 100644 --- a/api/src/main/java/io/minio/SetObjectLockConfigurationArgs.java +++ b/api/src/main/java/io/minio/SetObjectLockConfigurationArgs.java @@ -38,7 +38,7 @@ public static Builder builder() { public static final class Builder extends BucketArgs.Builder { private void validateConfig(ObjectLockConfiguration config) { - validateNotNull(config, "object-lock configuration"); + Utils.validateNotNull(config, "object-lock configuration"); } @Override diff --git a/api/src/main/java/io/minio/SetObjectRetentionArgs.java b/api/src/main/java/io/minio/SetObjectRetentionArgs.java index 00b363392..492fb4056 100644 --- a/api/src/main/java/io/minio/SetObjectRetentionArgs.java +++ b/api/src/main/java/io/minio/SetObjectRetentionArgs.java @@ -43,7 +43,7 @@ public static Builder builder() { public static final class Builder extends ObjectVersionArgs.Builder { private void validateConfig(Retention config) { - validateNotNull(config, "retention configuration"); + Utils.validateNotNull(config, "retention configuration"); } protected void validate(SetObjectRetentionArgs args) { diff --git a/api/src/main/java/io/minio/SetObjectTagsArgs.java b/api/src/main/java/io/minio/SetObjectTagsArgs.java index c832d94e1..edcd72acb 100644 --- a/api/src/main/java/io/minio/SetObjectTagsArgs.java +++ b/api/src/main/java/io/minio/SetObjectTagsArgs.java @@ -37,7 +37,7 @@ public static Builder builder() { /** Argument builder of {@link SetObjectTagsArgs}. */ public static final class Builder extends ObjectVersionArgs.Builder { private void validateTags(Tags tags) { - validateNotNull(tags, "tags"); + Utils.validateNotNull(tags, "tags"); } protected void validate(SetObjectTagsArgs args) { @@ -46,7 +46,7 @@ protected void validate(SetObjectTagsArgs args) { } public Builder tags(Map map) { - validateNotNull(map, "map for tags"); + Utils.validateNotNull(map, "map for tags"); operations.add(args -> args.tags = Tags.newObjectTags(map)); return this; } diff --git a/api/src/main/java/io/minio/Signer.java b/api/src/main/java/io/minio/Signer.java index c45f11f1a..8d29621a5 100644 --- a/api/src/main/java/io/minio/Signer.java +++ b/api/src/main/java/io/minio/Signer.java @@ -200,7 +200,7 @@ private void setCanonicalRequest() throws NoSuchAlgorithmException { + "\n" + this.contentSha256; - this.canonicalRequestHash = Digest.sha256Hash(this.canonicalRequest); + this.canonicalRequestHash = Checksum.hexString(Checksum.SHA256.sum(this.canonicalRequest)); } private void setStringToSign() { @@ -224,7 +224,7 @@ private void setChunkStringToSign() throws NoSuchAlgorithmException { + "\n" + this.prevSignature + "\n" - + Digest.sha256Hash("") + + Checksum.ZERO_SHA256_HASH + "\n" + this.contentSha256; } @@ -318,15 +318,15 @@ private void setPresignCanonicalRequest(int expires) throws NoSuchAlgorithmExcep HttpUrl.Builder urlBuilder = this.request.url().newBuilder(); urlBuilder.addEncodedQueryParameter( - S3Escaper.encode("X-Amz-Algorithm"), S3Escaper.encode("AWS4-HMAC-SHA256")); + Utils.encode("X-Amz-Algorithm"), Utils.encode("AWS4-HMAC-SHA256")); urlBuilder.addEncodedQueryParameter( - S3Escaper.encode("X-Amz-Credential"), S3Escaper.encode(this.accessKey + "/" + this.scope)); + Utils.encode("X-Amz-Credential"), Utils.encode(this.accessKey + "/" + this.scope)); urlBuilder.addEncodedQueryParameter( - S3Escaper.encode("X-Amz-Date"), S3Escaper.encode(this.date.format(Time.AMZ_DATE_FORMAT))); + Utils.encode("X-Amz-Date"), Utils.encode(this.date.format(Time.AMZ_DATE_FORMAT))); urlBuilder.addEncodedQueryParameter( - S3Escaper.encode("X-Amz-Expires"), S3Escaper.encode(Integer.toString(expires))); + Utils.encode("X-Amz-Expires"), Utils.encode(Integer.toString(expires))); urlBuilder.addEncodedQueryParameter( - S3Escaper.encode("X-Amz-SignedHeaders"), S3Escaper.encode(this.signedHeaders)); + Utils.encode("X-Amz-SignedHeaders"), Utils.encode(this.signedHeaders)); this.url = urlBuilder.build(); setCanonicalQueryString(); @@ -344,7 +344,7 @@ private void setPresignCanonicalRequest(int expires) throws NoSuchAlgorithmExcep + "\n" + this.contentSha256; - this.canonicalRequestHash = Digest.sha256Hash(this.canonicalRequest); + this.canonicalRequestHash = Checksum.hexString(Checksum.SHA256.sum(this.canonicalRequest)); } /** @@ -367,8 +367,7 @@ public static HttpUrl presignV4( return signer .url .newBuilder() - .addEncodedQueryParameter( - S3Escaper.encode("X-Amz-Signature"), S3Escaper.encode(signer.signature)) + .addEncodedQueryParameter(Utils.encode("X-Amz-Signature"), Utils.encode(signer.signature)) .build(); } diff --git a/api/src/main/java/io/minio/StatObjectArgs.java b/api/src/main/java/io/minio/StatObjectArgs.java index 522b4c540..da3365ef9 100644 --- a/api/src/main/java/io/minio/StatObjectArgs.java +++ b/api/src/main/java/io/minio/StatObjectArgs.java @@ -1,5 +1,5 @@ /* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * 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. @@ -17,24 +17,11 @@ package io.minio; /** Argument class of {@link MinioAsyncClient#statObject} and {@link MinioClient#statObject}. */ -public class StatObjectArgs extends ObjectConditionalReadArgs { - protected StatObjectArgs() {} - - public StatObjectArgs(ObjectReadArgs args) { - this.extraHeaders = args.extraHeaders; - this.extraQueryParams = args.extraQueryParams; - this.bucketName = args.bucketName; - this.region = args.region; - this.objectName = args.objectName; - this.versionId = args.versionId; - this.ssec = args.ssec; - } - +public class StatObjectArgs extends HeadObjectArgs { public static Builder builder() { return new Builder(); } - /** Argument builder of {@link StatObjectArgs}. */ - public static final class Builder - extends ObjectConditionalReadArgs.Builder {} + /** Argument builder of {@link HeadObjectArgs}. */ + public static final class Builder extends HeadObjectArgs.Builder {} } diff --git a/api/src/main/java/io/minio/StatObjectResponse.java b/api/src/main/java/io/minio/StatObjectResponse.java index 85cf8fe1e..50c7b2860 100644 --- a/api/src/main/java/io/minio/StatObjectResponse.java +++ b/api/src/main/java/io/minio/StatObjectResponse.java @@ -1,5 +1,5 @@ /* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * 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. @@ -16,113 +16,5 @@ package io.minio; -import io.minio.messages.LegalHold; -import io.minio.messages.ResponseDate; -import io.minio.messages.RetentionMode; -import java.time.ZonedDateTime; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import okhttp3.Headers; - -/** Response of {@link S3Base#statObjectAsync}. */ -public class StatObjectResponse extends GenericResponse { - private String etag; - private long size; - private ZonedDateTime lastModified; - private RetentionMode retentionMode; - private ZonedDateTime retentionRetainUntilDate; - private LegalHold legalHold; - private boolean deleteMarker; - private Map userMetadata; - - public StatObjectResponse(Headers headers, String bucket, String region, String object) { - super(headers, bucket, region, object); - String value; - - value = headers.get("ETag"); - this.etag = (value != null ? value.replaceAll("\"", "") : ""); - - value = headers.get("Content-Length"); - this.size = (value != null ? Long.parseLong(value) : -1); - - this.lastModified = - ZonedDateTime.parse(headers.get("Last-Modified"), Time.HTTP_HEADER_DATE_FORMAT); - - value = headers.get("x-amz-object-lock-mode"); - this.retentionMode = (value != null ? RetentionMode.valueOf(value) : null); - - value = headers.get("x-amz-object-lock-retain-until-date"); - this.retentionRetainUntilDate = - (value != null ? ResponseDate.fromString(value).zonedDateTime() : null); - - this.legalHold = new LegalHold("ON".equals(headers.get("x-amz-object-lock-legal-hold"))); - - this.deleteMarker = Boolean.parseBoolean(headers.get("x-amz-delete-marker")); - - Map userMetadata = new HashMap<>(); - for (String key : headers.names()) { - if (key.toLowerCase(Locale.US).startsWith("x-amz-meta-")) { - userMetadata.put( - key.toLowerCase(Locale.US).substring("x-amz-meta-".length(), key.length()), - headers.get(key)); - } - } - - this.userMetadata = Utils.unmodifiableMap(userMetadata); - } - - public String etag() { - return etag; - } - - public long size() { - return size; - } - - public ZonedDateTime lastModified() { - return lastModified; - } - - public RetentionMode retentionMode() { - return retentionMode; - } - - public ZonedDateTime retentionRetainUntilDate() { - return retentionRetainUntilDate; - } - - public LegalHold legalHold() { - return legalHold; - } - - public boolean deleteMarker() { - return deleteMarker; - } - - public String versionId() { - return this.headers().get("x-amz-version-id"); - } - - public String contentType() { - return this.headers().get("Content-Type"); - } - - public Map userMetadata() { - return userMetadata; - } - - @Override - public String toString() { - return "ObjectStat{" - + "bucket=" - + bucket() - + ", object=" - + object() - + ", last-modified=" - + lastModified - + ", size=" - + size - + "}"; - } -} +/** Response of {@link MinioAsyncClient#statObject}. */ +public class StatObjectResponse extends HeadObjectResponse {} diff --git a/api/src/main/java/io/minio/Time.java b/api/src/main/java/io/minio/Time.java index 01b788f68..06cfc75bf 100644 --- a/api/src/main/java/io/minio/Time.java +++ b/api/src/main/java/io/minio/Time.java @@ -17,9 +17,18 @@ package io.minio; +import com.fasterxml.jackson.annotation.JsonCreator; import java.time.ZoneId; +import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.temporal.ChronoField; import java.util.Locale; +import org.simpleframework.xml.Root; +import org.simpleframework.xml.convert.Convert; +import org.simpleframework.xml.convert.Converter; +import org.simpleframework.xml.stream.InputNode; +import org.simpleframework.xml.stream.OutputNode; /** Time formatters for S3 APIs. */ public class Time { @@ -28,7 +37,7 @@ public class Time { public static final DateTimeFormatter AMZ_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'", Locale.US).withZone(UTC); - public static final DateTimeFormatter RESPONSE_DATE_FORMAT = + public static final DateTimeFormatter ISO8601UTC_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH':'mm':'ss'.'SSS'Z'", Locale.US).withZone(UTC); // Formatted string is convertible to LocalDate only, not to LocalDateTime or ZonedDateTime. @@ -40,7 +49,54 @@ public class Time { public static final DateTimeFormatter HTTP_HEADER_DATE_FORMAT = DateTimeFormatter.ofPattern("EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'", Locale.US).withZone(UTC); - public static final DateTimeFormatter EXPIRATION_DATE_FORMAT = RESPONSE_DATE_FORMAT; - private Time() {} + + /** Wrapped {@link ZonedDateTime} to handle ISO8601UTC format. */ + @Root + @Convert(S3Time.S3TimeConverter.class) + public static class S3Time { + // ISO8601UTC format handles 0 or more digits of fraction-of-second + private static final DateTimeFormatter FORMAT = + new DateTimeFormatterBuilder() + .appendPattern("yyyy-MM-dd'T'HH':'mm':'ss") + .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true) + .appendPattern("'Z'") + .toFormatter(Locale.US) + .withZone(UTC); + + private ZonedDateTime value; + + public S3Time() {} + + public S3Time(ZonedDateTime value) { + this.value = value; + } + + public ZonedDateTime toZonedDateTime() { + return value; + } + + @Override + public String toString() { + return value == null ? null : value.format(ISO8601UTC_FORMAT); + } + + @JsonCreator + public static S3Time fromString(String value) { + return new S3Time(ZonedDateTime.parse(value, FORMAT)); + } + + /** XML converter class. */ + public static class S3TimeConverter implements Converter { + @Override + public S3Time read(InputNode node) throws Exception { + return S3Time.fromString(node.getValue()); + } + + @Override + public void write(OutputNode node, S3Time time) { + node.setValue(time.toString()); + } + } + } } diff --git a/api/src/main/java/io/minio/UploadObjectArgs.java b/api/src/main/java/io/minio/UploadObjectArgs.java index 68775b184..70f502b9d 100644 --- a/api/src/main/java/io/minio/UploadObjectArgs.java +++ b/api/src/main/java/io/minio/UploadObjectArgs.java @@ -58,7 +58,7 @@ protected void validate(UploadObjectArgs args) { } private void validateFilename(String filename) { - validateNotEmptyString(filename, "filename"); + Utils.validateNotEmptyString(filename, "filename"); if (!Files.isRegularFile(Paths.get(filename))) { throw new IllegalArgumentException(filename + " not a regular file"); } diff --git a/api/src/main/java/io/minio/UploadPartArgs.java b/api/src/main/java/io/minio/UploadPartArgs.java new file mode 100644 index 000000000..ee4e789c3 --- /dev/null +++ b/api/src/main/java/io/minio/UploadPartArgs.java @@ -0,0 +1,127 @@ +/* + * 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.RandomAccessFile; +import java.util.Objects; + +/** Argument class of {@link MinioAsyncClient#uploadPart} and {@link MinioClient#uploadPart}. */ +public class UploadPartArgs extends ObjectArgs { + private String uploadId; + private int partNumber; + private RandomAccessFile file; + private ByteBuffer buffer; + private byte[] data; + private Long length; + + public String uploadId() { + return uploadId; + } + + public int partNumber() { + return partNumber; + } + + public RandomAccessFile file() { + return file; + } + + public ByteBuffer buffer() { + return buffer; + } + + public byte[] data() { + return data; + } + + public Long length() { + return length; + } + + public static Builder builder() { + return new Builder(); + } + + /** Argument builder of {@link UploadPartArgs}. */ + public static final class Builder extends ObjectArgs.Builder { + @Override + protected void validate(UploadPartArgs args) { + super.validate(args); + Utils.validateNotEmptyString(args.uploadId, "upload ID"); + if (args.partNumber <= 0) { + throw new IllegalArgumentException("valid part number must be provided"); + } + if (!((args.file != null) != (args.buffer != null) != (args.data != null) + && !(args.file != null && args.buffer != null && args.data != null))) { + throw new IllegalArgumentException("only one of file, buffer or data must be provided"); + } + } + + public Builder uploadId(String uploadId) { + Utils.validateNotEmptyString(uploadId, "upload ID"); + operations.add(args -> args.uploadId = uploadId); + return this; + } + + public Builder partNumber(int partNumber) { + if (partNumber <= 0) throw new IllegalArgumentException("valid part number must be provided"); + operations.add(args -> args.partNumber = partNumber); + return this; + } + + public Builder file(RandomAccessFile file, long length) { + Utils.validateNotNull(file, "file"); + if (length < 0) throw new IllegalArgumentException("valid length must be provided"); + operations.add(args -> args.file = file); + operations.add(args -> args.length = length); + return this; + } + + public Builder buffer(ByteBuffer buffer) { + Utils.validateNotNull(buffer, "buffer"); + operations.add(args -> args.buffer = buffer); + return this; + } + + public Builder data(byte[] data, int length) { + Utils.validateNotNull(data, "data"); + if (length < 0) throw new IllegalArgumentException("valid length must be provided"); + operations.add(args -> args.data = data); + operations.add(args -> args.length = (long) length); + return this; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof UploadPartArgs)) return false; + if (!super.equals(o)) return false; + UploadPartArgs that = (UploadPartArgs) o; + return Objects.equals(uploadId, that.uploadId) + && Objects.equals(partNumber, that.partNumber) + && Objects.equals(file, that.file) + && Objects.equals(buffer, that.buffer) + && Objects.equals(data, that.data) + && Objects.equals(length, that.length); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), uploadId, partNumber, file, buffer, data, length); + } +} diff --git a/api/src/main/java/io/minio/UploadPartCopyArgs.java b/api/src/main/java/io/minio/UploadPartCopyArgs.java new file mode 100644 index 000000000..0d5891ace --- /dev/null +++ b/api/src/main/java/io/minio/UploadPartCopyArgs.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 com.google.common.collect.Multimap; +import java.util.Objects; + +/** + * Argument class of {@link MinioAsyncClient#uploadPartCopy} and {@link MinioClient#uploadPartCopy}. + */ +public class UploadPartCopyArgs extends ObjectArgs { + private String uploadId; + private int partNumber; + private Multimap headers; + + public String uploadId() { + return uploadId; + } + + public int partNumber() { + return partNumber; + } + + public Multimap headers() { + return headers; + } + + public static Builder builder() { + return new Builder(); + } + + /** Argument builder of {@link UploadPartCopyArgs}. */ + public static final class Builder extends ObjectArgs.Builder { + @Override + protected void validate(UploadPartCopyArgs args) { + super.validate(args); + Utils.validateNotEmptyString(args.uploadId, "upload ID"); + if (args.partNumber <= 0) { + throw new IllegalArgumentException("valid part number must be provided"); + } + Utils.validateNotNull(args.headers, "headers"); + if (args.headers.size() < 2) { + throw new IllegalArgumentException("valid headers must be provided"); + } + } + + public Builder uploadId(String uploadId) { + Utils.validateNotEmptyString(uploadId, "upload ID"); + operations.add(args -> args.uploadId = uploadId); + return this; + } + + public Builder partNumber(int partNumber) { + if (partNumber <= 0) throw new IllegalArgumentException("valid part number must be provided"); + operations.add(args -> args.partNumber = partNumber); + return this; + } + + public Builder headers(Multimap headers) { + Utils.validateNotNull(headers, "headers"); + if (headers.size() < 2) throw new IllegalArgumentException("valid headers must be provided"); + operations.add(args -> args.headers = headers); + return this; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof UploadPartCopyArgs)) return false; + if (!super.equals(o)) return false; + UploadPartCopyArgs that = (UploadPartCopyArgs) o; + return Objects.equals(uploadId, that.uploadId) + && Objects.equals(partNumber, that.partNumber) + && Objects.equals(headers, that.headers); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), uploadId, partNumber, headers); + } +} diff --git a/api/src/main/java/io/minio/UploadSnowballObjectsArgs.java b/api/src/main/java/io/minio/UploadSnowballObjectsArgs.java index 25b13751d..136fb204d 100644 --- a/api/src/main/java/io/minio/UploadSnowballObjectsArgs.java +++ b/api/src/main/java/io/minio/UploadSnowballObjectsArgs.java @@ -51,7 +51,7 @@ public static Builder builder() { public static final class Builder extends ObjectWriteArgs.Builder { private void validateObjects(Iterable objects) { - validateNotNull(objects, "objects"); + Utils.validateNotNull(objects, "objects"); } @Override diff --git a/api/src/main/java/io/minio/Utils.java b/api/src/main/java/io/minio/Utils.java index 1fe65c140..b483e40b2 100644 --- a/api/src/main/java/io/minio/Utils.java +++ b/api/src/main/java/io/minio/Utils.java @@ -16,19 +16,145 @@ package io.minio; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; +import com.google.common.escape.Escaper; +import com.google.common.net.UrlEscapers; +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; import java.io.UnsupportedEncodingException; +import java.lang.reflect.Array; +import java.net.URL; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.Enumeration; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; +import java.util.jar.Manifest; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import okhttp3.Headers; +import okhttp3.HttpUrl; /** Collection of utility functions. */ public class Utils { + private static final Escaper ESCAPER = UrlEscapers.urlPathSegmentEscaper(); + public static final String UTF_8 = StandardCharsets.UTF_8.toString(); + public static final String AWS_S3_PREFIX = + "^(((bucket\\.|accesspoint\\.)" + + "vpce(-(?!_)[a-z_\\d]+(? T validateNotNull(T arg, String argName) { + return Objects.requireNonNull(arg, argName + " must not be null"); + } + + public static void validateNotEmptyString(String arg, String argName) { + validateNotNull(arg, argName); + if (arg.isEmpty()) { + throw new IllegalArgumentException(argName + " must be a non-empty string."); + } + } + + public static void validateNullOrNotEmptyString(String arg, String argName) { + if (arg != null && arg.isEmpty()) { + throw new IllegalArgumentException(argName + " must be a non-empty string."); + } + } + + public static boolean isValidIPv4OrIPv6(String value) { + return InetAddressValidator.getInstance().isValid(value); + } + + public static boolean isValidIPv6(String value) { + return InetAddressValidator.getInstance().isValidInet6Address(value); + } + + public static boolean isValidIPv4(String value) { + return InetAddressValidator.getInstance().isValidInet4Address(value); + } + + public static void validateHostnameOrIPAddress(String endpoint) { + if (isValidIPv4OrIPv6(endpoint)) return; + + if (!HOSTNAME_REGEX.matcher(endpoint).find()) { + throw new IllegalArgumentException("invalid hostname " + endpoint); + } + } + + public static void validateUrl(HttpUrl url) { + if (!url.encodedPath().equals("/")) { + throw new IllegalArgumentException("no path allowed in endpoint " + url); + } + } + + public static HttpUrl getBaseUrl(String endpoint) { + validateNotEmptyString(endpoint, "endpoint"); + HttpUrl url = HttpUrl.parse(endpoint); + if (url == null) { + validateHostnameOrIPAddress(endpoint); + url = new HttpUrl.Builder().scheme("https").host(endpoint).build(); + } else { + validateUrl(url); + } + + return url; + } + + public static String getHostHeader(HttpUrl url) { + String host = url.host(); + if (isValidIPv6(host)) host = "[" + host + "]"; + + // ignore port when port and service matches i.e HTTP -> 80, HTTPS -> 443 + if ((url.scheme().equals("http") && url.port() == 80) + || (url.scheme().equals("https") && url.port() == 443)) { + return host; + } + + return host + ":" + url.port(); + } + public static String urlDecode(String value, String type) { if (!"url".equals(type)) return value; try { @@ -46,4 +172,643 @@ public static List unmodifiableList(List value) { public static Map unmodifiableMap(Map value) { return Collections.unmodifiableMap(value == null ? new HashMap() : value); } + + public static String stringify(Object value) { + if (value == null) return ""; + + if (value.getClass().isArray()) { + StringBuilder result = new StringBuilder("["); + + int length = Array.getLength(value); + + if (value.getClass().getComponentType().isPrimitive()) { + for (int i = 0; i < length; i++) { + if (i > 0) result.append(", "); + result.append(Array.get(value, i)); + } + } else { + for (int i = 0; i < length; i++) { + if (i > 0) result.append(", "); + Object element = Array.get(value, i); + result.append(stringify(element)); + } + } + + result.append("]"); + return result.toString(); + } + + if (value instanceof CharSequence) { + return "'" + value.toString() + "'"; + } + + return value.toString(); + } + + /** Merge two Multimaps. */ + public static Multimap mergeMultimap( + Multimap m1, Multimap m2) { + Multimap map = HashMultimap.create(); + if (m1 != null) map.putAll(m1); + if (m2 != null) map.putAll(m2); + return map; + } + + /** Create new HashMultimap by alternating keys and values. */ + public static Multimap newMultimap(String... keysAndValues) { + if (keysAndValues.length % 2 != 0) { + throw new IllegalArgumentException("Expected alternating keys and values"); + } + + Multimap map = HashMultimap.create(); + for (int i = 0; i < keysAndValues.length; i += 2) { + map.put(keysAndValues[i], keysAndValues[i + 1]); + } + + return map; + } + + /** Create new HashMultimap with copy of Map. */ + public static Multimap newMultimap(Map map) { + return (map != null) ? Multimaps.forMap(map) : HashMultimap.create(); + } + + /** Create new HashMultimap with copy of Multimap. */ + public static Multimap newMultimap(Multimap map) { + return (map != null) ? HashMultimap.create(map) : HashMultimap.create(); + } + + /** Convert Multimap to Headers. */ + public static Headers httpHeaders(Multimap headerMap) { + Headers.Builder builder = new Headers.Builder(); + if (headerMap == null) return builder.build(); + + if (headerMap.containsKey("Content-Encoding")) { + builder.add( + "Content-Encoding", + headerMap.get("Content-Encoding").stream() + .distinct() + .filter(encoding -> !encoding.isEmpty()) + .collect(Collectors.joining(","))); + } + + for (Map.Entry entry : headerMap.entries()) { + if (!entry.getKey().equals("Content-Encoding")) { + builder.addUnsafeNonAscii(entry.getKey(), entry.getValue()); + } + } + + return builder.build(); + } + + /** Returns S3 encoded string. */ + public static String encode(String str) { + if (str == null) { + return ""; + } + + StringBuilder builder = new StringBuilder(); + for (char ch : ESCAPER.escape(str).toCharArray()) { + switch (ch) { + case '!': + builder.append("%21"); + break; + case '$': + builder.append("%24"); + break; + case '&': + builder.append("%26"); + break; + case '\'': + builder.append("%27"); + break; + case '(': + builder.append("%28"); + break; + case ')': + builder.append("%29"); + break; + case '*': + builder.append("%2A"); + break; + case '+': + builder.append("%2B"); + break; + case ',': + builder.append("%2C"); + break; + case '/': + builder.append("%2F"); + break; + case ':': + builder.append("%3A"); + break; + case ';': + builder.append("%3B"); + break; + case '=': + builder.append("%3D"); + break; + case '@': + builder.append("%40"); + break; + case '[': + builder.append("%5B"); + break; + case ']': + builder.append("%5D"); + break; + default: + builder.append(ch); + } + } + return builder.toString(); + } + + /** Returns S3 encoded string of given path where multiple '/' are trimmed. */ + public static String encodePath(String path) { + final StringBuilder encodedPath = new StringBuilder(); + for (String pathSegment : path.split("/")) { + if (!pathSegment.isEmpty()) { + if (encodedPath.length() > 0) { + encodedPath.append("/"); + } + encodedPath.append(Utils.encode(pathSegment)); + } + } + + if (path.startsWith("/")) encodedPath.insert(0, "/"); + if (path.endsWith("/")) encodedPath.append("/"); + + return encodedPath.toString(); + } + + public static String getDefaultUserAgent() { + return String.format( + "MinIO (%s; %s) minio-java/%s", + System.getProperty("os.name"), + System.getProperty("os.arch"), + MinioProperties.INSTANCE.getVersion()); + } + + /** Identifies and stores version information of minio-java package at run time. */ + @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "MS_EXPOSE_REP") + public static enum MinioProperties { + INSTANCE; + + private static final Logger LOGGER = Logger.getLogger(MinioProperties.class.getName()); + + private final AtomicReference version = new AtomicReference<>(null); + + public String getVersion() { + String result = version.get(); + if (result != null) { + return result; + } + setVersion(); + return version.get(); + } + + private synchronized void setVersion() { + if (version.get() != null) { + return; + } + version.set("dev"); + ClassLoader classLoader = getClass().getClassLoader(); + if (classLoader == null) { + return; + } + + try { + Enumeration resources = classLoader.getResources("META-INF/MANIFEST.MF"); + while (resources.hasMoreElements()) { + try (InputStream is = resources.nextElement().openStream()) { + Manifest manifest = new Manifest(is); + if ("minio".equals(manifest.getMainAttributes().getValue("Implementation-Title"))) { + version.set(manifest.getMainAttributes().getValue("Implementation-Version")); + return; + } + } + } + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "IOException occurred", e); + version.set("unknown"); + } + } + } + + /* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + + /** + * Regular Expression validation (using JDK 1.4+ regex support). + * + *

Construct the validator either for a single regular expression or a set (array) of regular + * expressions. By default validation is case sensitive but constructors are provided to + * allow case in-sensitive validation. For example to create a validator which does case + * in-sensitive validation for a set of regular expressions: + * + *

+   * 
+   * String[] regexs = new String[] {...};
+   * RegexValidator validator = new RegexValidator(regexs, false);
+   * 
+   * 
+ * + *

+ * + *

    + *
  • Validate true or false: + *
  • + *
      + *
    • boolean valid = validator.isValid(value); + *
    + *
  • Validate returning an aggregated String of the matched groups: + *
  • + *
      + *
    • String result = validator.validate(value); + *
    + *
  • Validate returning the matched groups: + *
  • + *
      + *
    • String[] result = validator.match(value); + *
    + *
+ * + *

Note that patterns are matched against the entire input. + * + *

+ * + *

Cached instances pre-compile and re-use {@link Pattern}(s) - which according to the {@link + * Pattern} API are safe to use in a multi-threaded environment. + * + * @version $Revision$ + * @since Validator 1.4 + */ + public static class RegexValidator implements Serializable { + + private static final long serialVersionUID = -8832409930574867162L; + + private final Pattern[] patterns; + + /** + * Construct a case sensitive validator for a single regular expression. + * + * @param regex The regular expression this validator will validate against + */ + public RegexValidator(String regex) { + this(regex, true); + } + + /** + * Construct a validator for a single regular expression with the specified case sensitivity. + * + * @param regex The regular expression this validator will validate against + * @param caseSensitive when true matching is case sensitive, otherwise + * matching is case in-sensitive + */ + public RegexValidator(String regex, boolean caseSensitive) { + this(new String[] {regex}, caseSensitive); + } + + /** + * Construct a case sensitive validator that matches any one of the set of regular + * expressions. + * + * @param regexs The set of regular expressions this validator will validate against + */ + public RegexValidator(String[] regexs) { + this(regexs, true); + } + + /** + * Construct a validator that matches any one of the set of regular expressions with the + * specified case sensitivity. + * + * @param regexs The set of regular expressions this validator will validate against + * @param caseSensitive when true matching is case sensitive, otherwise + * matching is case in-sensitive + */ + public RegexValidator(String[] regexs, boolean caseSensitive) { + if (regexs == null || regexs.length == 0) { + throw new IllegalArgumentException("Regular expressions are missing"); + } + patterns = new Pattern[regexs.length]; + int flags = (caseSensitive ? 0 : Pattern.CASE_INSENSITIVE); + for (int i = 0; i < regexs.length; i++) { + if (regexs[i] == null || regexs[i].length() == 0) { + throw new IllegalArgumentException("Regular expression[" + i + "] is missing"); + } + patterns[i] = Pattern.compile(regexs[i], flags); + } + } + + /** + * Validate a value against the set of regular expressions. + * + * @param value The value to validate. + * @return true if the value is valid otherwise false. + */ + public boolean isValid(String value) { + if (value == null) { + return false; + } + for (int i = 0; i < patterns.length; i++) { + if (patterns[i].matcher(value).matches()) { + return true; + } + } + return false; + } + + /** + * Validate a value against the set of regular expressions returning the array of matched + * groups. + * + * @param value The value to validate. + * @return String array of the groups matched if valid or null if invalid + */ + @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( + value = "PZLA", + justification = "Null is checked, not empty array. API is clear as well.") + public String[] match(String value) { + if (value == null) { + return null; + } + for (int i = 0; i < patterns.length; i++) { + Matcher matcher = patterns[i].matcher(value); + if (matcher.matches()) { + int count = matcher.groupCount(); + String[] groups = new String[count]; + for (int j = 0; j < count; j++) { + groups[j] = matcher.group(j + 1); + } + return groups; + } + } + return null; + } + + /** + * Validate a value against the set of regular expressions returning a String value of the + * aggregated groups. + * + * @param value The value to validate. + * @return Aggregated String value comprised of the groups matched if valid or null + * if invalid + */ + public String validate(String value) { + if (value == null) { + return null; + } + for (int i = 0; i < patterns.length; i++) { + Matcher matcher = patterns[i].matcher(value); + if (matcher.matches()) { + int count = matcher.groupCount(); + if (count == 1) { + return matcher.group(1); + } + StringBuilder buffer = new StringBuilder(); + for (int j = 0; j < count; j++) { + String component = matcher.group(j + 1); + if (component != null) { + buffer.append(component); + } + } + return buffer.toString(); + } + } + return null; + } + + /** + * Provide a String representation of this validator. + * + * @return A String representation of this validator + */ + @Override + public String toString() { + StringBuilder buffer = new StringBuilder(); + buffer.append("RegexValidator{"); + for (int i = 0; i < patterns.length; i++) { + if (i > 0) { + buffer.append(","); + } + buffer.append(patterns[i].pattern()); + } + buffer.append("}"); + return buffer.toString(); + } + } + + /* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + + /** + * InetAddress validation and conversion routines (java.net.InetAddress). + * + *

+ * + *

+ * + *

This class provides methods to validate a candidate IP address. + * + *

+ * + *

This class is a Singleton; you can retrieve the instance via the {@link #getInstance()} + * method. + * + * @version $Revision$ + * @since Validator 1.4 + */ + public static class InetAddressValidator { + + private static final int IPV4_MAX_OCTET_VALUE = 255; + + private static final int MAX_UNSIGNED_SHORT = 0xffff; + + private static final int BASE_16 = 16; + + private static final long serialVersionUID = -919201640201914789L; + + private static final String IPV4_REGEX = "^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$"; + + // Max number of hex groups (separated by :) in an IPV6 address + private static final int IPV6_MAX_HEX_GROUPS = 8; + + // Max hex digits in each IPv6 group + private static final int IPV6_MAX_HEX_DIGITS_PER_GROUP = 4; + + /** Singleton instance of this class. */ + private static final InetAddressValidator VALIDATOR = new InetAddressValidator(); + + /** IPv4 RegexValidator. */ + private final RegexValidator ipv4Validator = new RegexValidator(IPV4_REGEX); + + private InetAddressValidator() {} + + /** + * Returns the singleton instance of this validator. + * + * @return the singleton instance of this validator + */ + public static InetAddressValidator getInstance() { + return VALIDATOR; + } + + /** + * Checks if the specified string is a valid IP address. + * + * @param inetAddress the string to validate + * @return true if the string validates as an IP address + */ + public boolean isValid(String inetAddress) { + return isValidInet4Address(inetAddress) || isValidInet6Address(inetAddress); + } + + /** + * Validates an IPv4 address. Returns true if valid. + * + * @param inet4Address the IPv4 address to validate + * @return true if the argument contains a valid IPv4 address + */ + public boolean isValidInet4Address(String inet4Address) { + // verify that address conforms to generic IPv4 format + String[] groups = ipv4Validator.match(inet4Address); + + if (groups == null) { + return false; + } + + // verify that address subgroups are legal + for (String ipSegment : groups) { + if (ipSegment == null || ipSegment.length() == 0) { + return false; + } + + int iIpSegment = 0; + + try { + iIpSegment = Integer.parseInt(ipSegment); + } catch (NumberFormatException e) { + return false; + } + + if (iIpSegment > IPV4_MAX_OCTET_VALUE) { + return false; + } + + if (ipSegment.length() > 1 && ipSegment.startsWith("0")) { + return false; + } + } + + return true; + } + + /** + * Validates an IPv6 address. Returns true if valid. + * + * @param inet6Address the IPv6 address to validate + * @return true if the argument contains a valid IPv6 address + * @since 1.4.1 + */ + public boolean isValidInet6Address(String inet6Address) { + boolean containsCompressedZeroes = inet6Address.contains("::"); + if (containsCompressedZeroes + && inet6Address.indexOf("::") != inet6Address.lastIndexOf("::")) { + return false; + } + if (inet6Address.startsWith(":") && !inet6Address.startsWith("::") + || inet6Address.endsWith(":") && !inet6Address.endsWith("::")) { + return false; + } + String[] octets = inet6Address.split(":"); + if (containsCompressedZeroes) { + List octetList = new ArrayList(Arrays.asList(octets)); + if (inet6Address.endsWith("::")) { + // String.split() drops ending empty segments + octetList.add(""); + } else if (inet6Address.startsWith("::") && !octetList.isEmpty()) { + octetList.remove(0); + } + octets = octetList.toArray(new String[octetList.size()]); + } + if (octets.length > IPV6_MAX_HEX_GROUPS) { + return false; + } + int validOctets = 0; + int emptyOctets = 0; + for (int index = 0; index < octets.length; index++) { + String octet = octets[index]; + if (octet.length() == 0) { + emptyOctets++; + if (emptyOctets > 1) { + return false; + } + } else { + emptyOctets = 0; + if (octet.contains(".")) { // contains is Java 1.5+ + if (!inet6Address.endsWith(octet)) { + return false; + } + if (index > octets.length - 1 || index > 6) { // CHECKSTYLE IGNORE MagicNumber + // IPV4 occupies last two octets + return false; + } + if (!isValidInet4Address(octet)) { + return false; + } + validOctets += 2; + continue; + } + if (octet.length() > IPV6_MAX_HEX_DIGITS_PER_GROUP) { + return false; + } + int octetInt = 0; + try { + octetInt = Integer.valueOf(octet, BASE_16).intValue(); + } catch (NumberFormatException e) { + return false; + } + if (octetInt < 0 || octetInt > MAX_UNSIGNED_SHORT) { + return false; + } + } + validOctets++; + } + if (validOctets < IPV6_MAX_HEX_GROUPS && !containsCompressedZeroes) { + return false; + } + return true; + } + } } diff --git a/api/src/main/java/io/minio/credentials/AssumeRoleProvider.java b/api/src/main/java/io/minio/credentials/AssumeRoleProvider.java index b94496994..1f7516481 100644 --- a/api/src/main/java/io/minio/credentials/AssumeRoleProvider.java +++ b/api/src/main/java/io/minio/credentials/AssumeRoleProvider.java @@ -16,7 +16,7 @@ package io.minio.credentials; -import io.minio.Digest; +import io.minio.Checksum; import io.minio.Signer; import io.minio.Time; import java.security.InvalidKeyException; @@ -95,7 +95,7 @@ public AssumeRoleProvider( } String data = urlBuilder.build().encodedQuery(); - this.contentSha256 = Digest.sha256Hash(data); + this.contentSha256 = Checksum.hexString(Checksum.SHA256.sum(data)); this.request = new Request.Builder() .url(url) diff --git a/api/src/main/java/io/minio/credentials/Credentials.java b/api/src/main/java/io/minio/credentials/Credentials.java index fb28da816..00d20bd48 100644 --- a/api/src/main/java/io/minio/credentials/Credentials.java +++ b/api/src/main/java/io/minio/credentials/Credentials.java @@ -18,7 +18,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import io.minio.messages.ResponseDate; +import io.minio.Time; import java.time.Duration; import java.time.ZonedDateTime; import java.util.Objects; @@ -45,13 +45,13 @@ public class Credentials { @Element(name = "Expiration") @JsonProperty("expiration") - private final ResponseDate expiration; + private final Time.S3Time expiration; public Credentials( @Nonnull @Element(name = "AccessKeyId") @JsonProperty("accessKey") String accessKey, @Nonnull @Element(name = "SecretAccessKey") @JsonProperty("secretKey") String secretKey, @Nullable @Element(name = "SessionToken") @JsonProperty("sessionToken") String sessionToken, - @Nullable @Element(name = "Expiration") @JsonProperty("expiration") ResponseDate expiration) { + @Nullable @Element(name = "Expiration") @JsonProperty("expiration") Time.S3Time expiration) { this.accessKey = Objects.requireNonNull(accessKey, "AccessKey must not be null"); this.secretKey = Objects.requireNonNull(secretKey, "SecretKey must not be null"); if (accessKey.isEmpty() || secretKey.isEmpty()) { @@ -73,11 +73,15 @@ public String sessionToken() { return sessionToken; } + public ZonedDateTime expiration() { + return expiration == null ? null : expiration.toZonedDateTime(); + } + public boolean isExpired() { if (expiration == null) { return false; } - return ZonedDateTime.now().plus(Duration.ofSeconds(10)).isAfter(expiration.zonedDateTime()); + return ZonedDateTime.now().plus(Duration.ofSeconds(10)).isAfter(expiration.toZonedDateTime()); } } diff --git a/api/src/main/java/io/minio/credentials/IamAwsProvider.java b/api/src/main/java/io/minio/credentials/IamAwsProvider.java index 08abb1050..08419ac8f 100644 --- a/api/src/main/java/io/minio/credentials/IamAwsProvider.java +++ b/api/src/main/java/io/minio/credentials/IamAwsProvider.java @@ -21,7 +21,7 @@ import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; -import io.minio.messages.ResponseDate; +import io.minio.Time; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; @@ -248,7 +248,7 @@ public static class EcsCredentials { private String sessionToken; @JsonProperty("Expiration") - private ResponseDate expiration; + private Time.S3Time expiration; @JsonProperty("Code") private String code; diff --git a/api/src/main/java/io/minio/errors/ErrorResponseException.java b/api/src/main/java/io/minio/errors/ErrorResponseException.java index d2755932d..f62d66976 100644 --- a/api/src/main/java/io/minio/errors/ErrorResponseException.java +++ b/api/src/main/java/io/minio/errors/ErrorResponseException.java @@ -16,9 +16,7 @@ package io.minio.errors; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.minio.messages.ErrorResponse; -import okhttp3.Request; import okhttp3.Response; /** Thrown to indicate that error response is received when executing Amazon S3 operation. */ @@ -28,8 +26,8 @@ public class ErrorResponseException extends MinioException { private final ErrorResponse errorResponse; - @SuppressFBWarnings( - value = "Se", + @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( + value = "SE_BAD_FIELD", justification = "There's really no excuse except that nobody has complained") private final Response response; @@ -51,30 +49,19 @@ public Response response() { @Override public String toString() { - Request request = response.request(); - return "error occurred\n" - + errorResponse.toString() - + "\n" - + "request={" - + "method=" - + request.method() - + ", " - + "url=" - + request.url() - + ", " - + "headers=" - + request + return String.format( + "S3 operation failed; ErrorResponseException{errorResponse=%s, request={method=%s, url=%s," + + " headers=%s}, response={code=%s, headers=%s}}", + errorResponse.toString(), + response.request().method(), + response.request().url(), + response + .request() .headers() .toString() .replaceAll("Signature=([0-9a-f]+)", "Signature=*REDACTED*") - .replaceAll("Credential=([^/]+)", "Credential=*REDACTED*") - + "}\n" - + "response={" - + "code=" - + response.code() - + ", " - + "headers=" - + response.headers() - + "}\n"; + .replaceAll("Credential=([^/]+)", "Credential=*REDACTED*"), + response.code(), + response.headers().toString()); } } diff --git a/api/src/main/java/io/minio/http/HttpUtils.java b/api/src/main/java/io/minio/http/HttpUtils.java deleted file mode 100644 index b69f3d2c2..000000000 --- a/api/src/main/java/io/minio/http/HttpUtils.java +++ /dev/null @@ -1,335 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2021 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.http; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import io.minio.org.apache.commons.validator.routines.InetAddressValidator; -import java.io.FileInputStream; -import java.io.IOException; -import java.security.GeneralSecurityException; -import java.security.KeyManagementException; -import java.security.KeyStore; -import java.security.NoSuchAlgorithmException; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.util.Arrays; -import java.util.Collection; -import java.util.concurrent.TimeUnit; -import java.util.regex.Pattern; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Protocol; - -/** HTTP utilities. */ -public class HttpUtils { - public static final String AWS_S3_PREFIX = - "^(((bucket\\.|accesspoint\\.)" - + "vpce(-(?!_)[a-z_\\d]+(? 80, HTTPS -> 443 - if ((url.scheme().equals("http") && url.port() == 80) - || (url.scheme().equals("https") && url.port() == 443)) { - return host; - } - - return host + ":" + url.port(); - } - - private static OkHttpClient enableJKSPKCS12Certificates( - OkHttpClient httpClient, - String trustStorePath, - String trustStorePassword, - String keyStorePath, - String keyStorePassword, - String keyStoreType) - throws GeneralSecurityException, IOException { - if (trustStorePath == null || trustStorePath.isEmpty()) { - throw new IllegalArgumentException("trust store path must be provided"); - } - if (trustStorePassword == null) { - throw new IllegalArgumentException("trust store password must be provided"); - } - if (keyStorePath == null || keyStorePath.isEmpty()) { - throw new IllegalArgumentException("key store path must be provided"); - } - if (keyStorePassword == null) { - throw new IllegalArgumentException("key store password must be provided"); - } - - SSLContext sslContext = SSLContext.getInstance("TLS"); - KeyStore trustStore = KeyStore.getInstance("JKS"); - KeyStore keyStore = KeyStore.getInstance(keyStoreType); - try (FileInputStream trustInput = new FileInputStream(trustStorePath); - FileInputStream keyInput = new FileInputStream(keyStorePath); ) { - trustStore.load(trustInput, trustStorePassword.toCharArray()); - keyStore.load(keyInput, keyStorePassword.toCharArray()); - } - TrustManagerFactory trustManagerFactory = - TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init(trustStore); - - KeyManagerFactory keyManagerFactory = - KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - keyManagerFactory.init(keyStore, keyStorePassword.toCharArray()); - - sslContext.init( - keyManagerFactory.getKeyManagers(), - trustManagerFactory.getTrustManagers(), - new java.security.SecureRandom()); - - return httpClient - .newBuilder() - .sslSocketFactory( - sslContext.getSocketFactory(), - (X509TrustManager) trustManagerFactory.getTrustManagers()[0]) - .build(); - } - - public static OkHttpClient enableJKSCertificates( - OkHttpClient httpClient, - String trustStorePath, - String trustStorePassword, - String keyStorePath, - String keyStorePassword) - throws GeneralSecurityException, IOException { - return enableJKSPKCS12Certificates( - httpClient, trustStorePath, trustStorePassword, keyStorePath, keyStorePassword, "JKS"); - } - - public static OkHttpClient enablePKCS12Certificates( - OkHttpClient httpClient, - String trustStorePath, - String trustStorePassword, - String keyStorePath, - String keyStorePassword) - throws GeneralSecurityException, IOException { - return enableJKSPKCS12Certificates( - httpClient, trustStorePath, trustStorePassword, keyStorePath, keyStorePassword, "PKCS12"); - } - - /** - * copied logic from - * https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/CustomTrust.java - */ - public static OkHttpClient enableExternalCertificates(OkHttpClient httpClient, String filename) - throws GeneralSecurityException, IOException { - Collection certificates = null; - try (FileInputStream fis = new FileInputStream(filename)) { - certificates = CertificateFactory.getInstance("X.509").generateCertificates(fis); - } - - if (certificates == null || certificates.isEmpty()) { - throw new IllegalArgumentException("expected non-empty set of trusted certificates"); - } - - char[] password = "password".toCharArray(); // Any password will work. - - // Put the certificates a key store. - KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); - // By convention, 'null' creates an empty key store. - keyStore.load(null, password); - - int index = 0; - for (Certificate certificate : certificates) { - String certificateAlias = Integer.toString(index++); - keyStore.setCertificateEntry(certificateAlias, certificate); - } - - // Use it to build an X509 trust manager. - KeyManagerFactory keyManagerFactory = - KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - keyManagerFactory.init(keyStore, password); - TrustManagerFactory trustManagerFactory = - TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init(keyStore); - - final KeyManager[] keyManagers = keyManagerFactory.getKeyManagers(); - final TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); - - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(keyManagers, trustManagers, null); - SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); - - return httpClient - .newBuilder() - .sslSocketFactory(sslSocketFactory, (X509TrustManager) trustManagers[0]) - .build(); - } - - public static OkHttpClient newDefaultHttpClient( - long connectTimeout, long writeTimeout, long readTimeout) { - OkHttpClient httpClient = - new OkHttpClient() - .newBuilder() - .connectTimeout(connectTimeout, TimeUnit.MILLISECONDS) - .writeTimeout(writeTimeout, TimeUnit.MILLISECONDS) - .readTimeout(readTimeout, TimeUnit.MILLISECONDS) - .protocols(Arrays.asList(Protocol.HTTP_1_1)) - .build(); - String filename = System.getenv("SSL_CERT_FILE"); - if (filename != null && !filename.isEmpty()) { - try { - httpClient = enableExternalCertificates(httpClient, filename); - } catch (GeneralSecurityException | IOException e) { - throw new RuntimeException(e); - } - } - return httpClient; - } - - @SuppressFBWarnings(value = "SIC", justification = "Should not be used in production anyways.") - public static OkHttpClient disableCertCheck(OkHttpClient client) - throws KeyManagementException, NoSuchAlgorithmException { - final TrustManager[] trustAllCerts = - new TrustManager[] { - new X509TrustManager() { - @Override - public void checkClientTrusted(X509Certificate[] chain, String authType) - throws CertificateException {} - - @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) - throws CertificateException {} - - @Override - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[] {}; - } - } - }; - - final SSLContext sslContext = SSLContext.getInstance("SSL"); - sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); - final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); - - return client - .newBuilder() - .sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]) - .hostnameVerifier( - new HostnameVerifier() { - @Override - public boolean verify(String hostname, SSLSession session) { - return true; - } - }) - .build(); - } - - public static OkHttpClient setTimeout( - OkHttpClient client, long connectTimeout, long writeTimeout, long readTimeout) { - return client - .newBuilder() - .connectTimeout(connectTimeout, TimeUnit.MILLISECONDS) - .writeTimeout(writeTimeout, TimeUnit.MILLISECONDS) - .readTimeout(readTimeout, TimeUnit.MILLISECONDS) - .build(); - } -} diff --git a/api/src/main/java/io/minio/http/Method.java b/api/src/main/java/io/minio/http/Method.java deleted file mode 100644 index e5224f813..000000000 --- a/api/src/main/java/io/minio/http/Method.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2015 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.http; - -/** HTTP methods. */ -public enum Method { - GET, - HEAD, - POST, - PUT, - DELETE; -} diff --git a/api/src/main/java/io/minio/messages/AbortIncompleteMultipartUpload.java b/api/src/main/java/io/minio/messages/AbortIncompleteMultipartUpload.java deleted file mode 100644 index 8704396c2..000000000 --- a/api/src/main/java/io/minio/messages/AbortIncompleteMultipartUpload.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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.messages; - -import org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** - * Helper class to denote abort incomplete multipart upload information for {@link LifecycleRule}. - */ -@Root(name = "AbortIncompleteMultipartUpload") -public class AbortIncompleteMultipartUpload { - @Element(name = "DaysAfterInitiation") - private int daysAfterInitiation; - - public AbortIncompleteMultipartUpload( - @Element(name = "DaysAfterInitiation") int daysAfterInitiation) { - this.daysAfterInitiation = daysAfterInitiation; - } - - public int daysAfterInitiation() { - return daysAfterInitiation; - } -} diff --git a/api/src/main/java/io/minio/messages/AccessControlList.java b/api/src/main/java/io/minio/messages/AccessControlList.java index fff7fc582..27e3e9c13 100644 --- a/api/src/main/java/io/minio/messages/AccessControlList.java +++ b/api/src/main/java/io/minio/messages/AccessControlList.java @@ -20,10 +20,21 @@ import java.util.List; 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.ElementList; +import org.simpleframework.xml.Namespace; import org.simpleframework.xml.Root; +import org.simpleframework.xml.convert.Convert; +import org.simpleframework.xml.convert.Converter; +import org.simpleframework.xml.stream.InputNode; +import org.simpleframework.xml.stream.OutputNode; -/** Helper class to denote access control list of {@link S3OutputLocation}. */ +/** + * Helper class to denote access control list of {@link RestoreRequest.S3} and {@link + * AccessControlPolicy}. + */ @Root(name = "AccessControlList") @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") public class AccessControlList { @@ -42,4 +53,182 @@ public AccessControlList( public List grants() { return Utils.unmodifiableList(grants); } + + @Override + public String toString() { + return String.format("AccessControlList{grants=%s}", Utils.stringify(grants)); + } + + @Root(name = "Grant") + @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") + public static class Grant { + @Element(name = "Grantee", required = false) + private Grantee grantee; + + @Element(name = "Permission", required = false) + private 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(); + } + + @Override + public String toString() { + return String.format( + "Grant{grantee=%s, permission=%s}", + Utils.stringify(grantee), Utils.stringify(permission)); + } + } + + @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 static class Grantee { + @Attribute(name = "type") + private String xsiType; + + @Element(name = "DisplayName", required = false) + private String displayName; + + @Element(name = "EmailAddress", required = false) + private String emailAddress; + + @Element(name = "ID", required = false) + private String id; + + @Element(name = "Type") + private Type type; + + @Element(name = "URI", required = false) + private String uri; + + public Grantee( + @Nonnull Type type, + @Nullable String displayName, + @Nullable String emailAddress, + @Nullable String id, + @Nullable String uri) { + this.type = Objects.requireNonNull(type, "Type must not be null"); + this.displayName = displayName; + this.emailAddress = emailAddress; + this.id = id; + this.uri = uri; + } + + public Grantee( + @Nonnull @Attribute(name = "type") String xsiType, + @Nonnull @Element(name = "Type") Type 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 Type type() { + return type; + } + + public String uri() { + return uri; + } + + @Override + public String toString() { + return String.format( + "Grantee{xsiType=%s, displayName=%s, emailAddress=%s, id=%s, type=%s, uri=%s}", + xsiType, + Utils.stringify(displayName), + Utils.stringify(emailAddress), + Utils.stringify(id), + Utils.stringify(type), + Utils.stringify(uri)); + } + } + + @Root(name = "Type") + @Convert(Type.TypeConverter.class) + public static enum Type { + CANONICAL_USER("CanonicalUser"), + AMAZON_CUSTOMER_BY_EMAIL("AmazonCustomerByEmail"), + GROUP("Group"); + + private final String value; + + private Type(String value) { + this.value = value; + } + + public String toString() { + return this.value; + } + + /** Returns Type of given string. */ + public static Type fromString(String granteeTypeString) { + for (Type granteeType : Type.values()) { + if (granteeTypeString.equals(granteeType.value)) { + return granteeType; + } + } + + throw new IllegalArgumentException("Unknown grantee type '" + granteeTypeString + "'"); + } + + /** XML converter class. */ + public static class TypeConverter implements Converter { + @Override + public Type read(InputNode node) throws Exception { + return Type.fromString(node.getValue()); + } + + @Override + public void write(OutputNode node, Type granteeType) throws Exception { + node.setValue(granteeType.toString()); + } + } + } + + @Root(name = "Permission") + public static enum Permission { + FULL_CONTROL, + WRITE, + WRITE_ACP, + READ, + READ_ACP; + } } diff --git a/api/src/main/java/io/minio/messages/AccessControlPolicy.java b/api/src/main/java/io/minio/messages/AccessControlPolicy.java index f9b95921e..3af01a2c1 100644 --- a/api/src/main/java/io/minio/messages/AccessControlPolicy.java +++ b/api/src/main/java/io/minio/messages/AccessControlPolicy.java @@ -18,6 +18,7 @@ import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; +import io.minio.Utils; import java.util.List; import org.simpleframework.xml.Element; import org.simpleframework.xml.Root; @@ -54,18 +55,20 @@ public AccessControlList accessControlList() { public String cannedAcl() { if (accessControlList == null) return ""; - List grants = accessControlList.grants(); + List grants = accessControlList.grants(); int size = grants.size(); if (size < 1 || size > 3) return ""; - for (Grant grant : grants) { + for (AccessControlList.Grant grant : grants) { if (grant == null) continue; String uri = grant.granteeUri(); - if (grant.permission() == Permission.FULL_CONTROL && size == 1 && "".equals(uri)) { + if (grant.permission() == AccessControlList.Permission.FULL_CONTROL + && size == 1 + && "".equals(uri)) { return "private"; - } else if (grant.permission() == Permission.READ && size == 2) { + } else if (grant.permission() == AccessControlList.Permission.READ && size == 2) { if ("http://acs.amazonaws.com/groups/global/AuthenticatedUsers".equals(uri)) { return "authenticated-read"; } @@ -75,7 +78,7 @@ public String cannedAcl() { && owner.id().equals(grant.granteeId())) { return "bucket-owner-read"; } - } else if (grant.permission() == Permission.WRITE + } else if (grant.permission() == AccessControlList.Permission.WRITE && size == 3 && "http://acs.amazonaws.com/groups/global/AllUsers".equals(uri)) { return "public-read-write"; @@ -90,19 +93,19 @@ public Multimap grantAcl() { if (accessControlList != null) { map = HashMultimap.create(); - for (Grant grant : accessControlList.grants()) { + for (AccessControlList.Grant grant : accessControlList.grants()) { if (grant == null) continue; String value = "id=" + grant.granteeId(); - if (grant.permission() == Permission.READ) { + if (grant.permission() == AccessControlList.Permission.READ) { map.put("X-Amz-Grant-Read", value); - } else if (grant.permission() == Permission.WRITE) { + } else if (grant.permission() == AccessControlList.Permission.WRITE) { map.put("X-Amz-Grant-Write", value); - } else if (grant.permission() == Permission.READ_ACP) { + } else if (grant.permission() == AccessControlList.Permission.READ_ACP) { map.put("X-Amz-Grant-Read-Acp", value); - } else if (grant.permission() == Permission.WRITE_ACP) { + } else if (grant.permission() == AccessControlList.Permission.WRITE_ACP) { map.put("X-Amz-Grant-Write-Acp", value); - } else if (grant.permission() == Permission.FULL_CONTROL) { + } else if (grant.permission() == AccessControlList.Permission.FULL_CONTROL) { map.put("X-Amz-Grant-Full-Control", value); } } @@ -110,4 +113,11 @@ public Multimap grantAcl() { return map; } + + @Override + public String toString() { + return String.format( + "AccessControlPolicy(owner=%s, accessControlList=%s)", + Utils.stringify(owner), Utils.stringify(accessControlList)); + } } diff --git a/api/src/main/java/io/minio/messages/AccessControlTranslation.java b/api/src/main/java/io/minio/messages/AccessControlTranslation.java deleted file mode 100644 index 0461e05f0..000000000 --- a/api/src/main/java/io/minio/messages/AccessControlTranslation.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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.messages; - -import java.util.Objects; -import javax.annotation.Nonnull; -import org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** - * Helper class to denote access control translation information for {@link ReplicationDestination}. - */ -@Root(name = "AccessControlTranslation") -public class AccessControlTranslation { - @Element(name = "Owner") - private String owner = "Destination"; - - public AccessControlTranslation(@Nonnull @Element(name = "Owner") String owner) { - this.owner = Objects.requireNonNull(owner, "Owner must not be null"); - } - - public String owner() { - return this.owner; - } -} diff --git a/api/src/main/java/io/minio/messages/AndOperator.java b/api/src/main/java/io/minio/messages/AndOperator.java deleted file mode 100644 index 0e6774e2e..000000000 --- a/api/src/main/java/io/minio/messages/AndOperator.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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.messages; - -import io.minio.Utils; -import java.util.Map; -import javax.annotation.Nullable; -import org.simpleframework.xml.Element; -import org.simpleframework.xml.ElementMap; -import org.simpleframework.xml.Root; -import org.simpleframework.xml.convert.Convert; - -/** Helper class to denote AND operator information for {@link RuleFilter}. */ -@Root(name = "And") -public class AndOperator { - @Element(name = "Prefix", required = false) - @Convert(PrefixConverter.class) - private String prefix; - - @Element(name = "ObjectSizeLessThan", required = false) - private Long objectSizeLessThan; - - @Element(name = "ObjectSizeGreaterThan", required = false) - private Long objectSizeGreaterThan; - - @ElementMap( - attribute = false, - entry = "Tag", - inline = true, - key = "Key", - value = "Value", - required = false) - private Map tags; - - public AndOperator( - @Nullable @Element(name = "Prefix", required = false) String prefix, - @Nullable - @ElementMap( - attribute = false, - entry = "Tag", - inline = true, - key = "Key", - value = "Value", - required = false) - Map tags) { - if (prefix == null && tags == null) { - throw new IllegalArgumentException("At least Prefix or Tags must be set"); - } - - if (tags != null) { - for (String key : tags.keySet()) { - if (key.isEmpty()) { - throw new IllegalArgumentException("Tags must not contain empty key"); - } - } - } - - this.prefix = prefix; - this.tags = Utils.unmodifiableMap(tags); - } - - public AndOperator( - @Nullable @Element(name = "Prefix", required = false) String prefix, - @Nullable - @ElementMap( - attribute = false, - entry = "Tag", - inline = true, - key = "Key", - value = "Value", - required = false) - Map tags, - @Nullable @Element(name = "ObjectSizeLessThan", required = false) Long objectSizeLessThan, - @Nullable @Element(name = "ObjectSizeGreaterThan", required = false) - Long objectSizeGreaterThan) { - this(prefix, tags); - this.objectSizeLessThan = objectSizeLessThan; - this.objectSizeGreaterThan = objectSizeGreaterThan; - } - - public String prefix() { - return this.prefix; - } - - public Long objectSizeLessThan() { - return this.objectSizeLessThan; - } - - public Long objectSizeGreaterThan() { - return this.objectSizeGreaterThan; - } - - public Map tags() { - return this.tags; - } -} diff --git a/api/src/main/java/io/minio/messages/BasePartsResult.java b/api/src/main/java/io/minio/messages/BasePartsResult.java new file mode 100644 index 000000000..3ae6b5667 --- /dev/null +++ b/api/src/main/java/io/minio/messages/BasePartsResult.java @@ -0,0 +1,78 @@ +/* + * 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 org.simpleframework.xml.Element; +import org.simpleframework.xml.ElementList; +import org.simpleframework.xml.Root; + +/** + * Common part information for {@link ListPartsResult} and {@link + * GetObjectAttributesOutput.ObjectParts}. + */ +@Root(name = "BasePartsResult", strict = false) +public abstract class BasePartsResult { + @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; + + public BasePartsResult() {} + + 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); + } + + @Override + public String toString() { + return String.format( + "isTruncated=%s, maxParts=%s, nextPartNumberMarker=%s, partNumberMarker=%s, parts=%s", + Utils.stringify(isTruncated), + Utils.stringify(maxParts), + Utils.stringify(nextPartNumberMarker), + Utils.stringify(partNumberMarker), + Utils.stringify(parts)); + } +} diff --git a/api/src/main/java/io/minio/messages/SelectObjectContentRequestBase.java b/api/src/main/java/io/minio/messages/BaseSelectParameters.java similarity index 89% rename from api/src/main/java/io/minio/messages/SelectObjectContentRequestBase.java rename to api/src/main/java/io/minio/messages/BaseSelectParameters.java index 3bce8fc8b..436f86a4a 100644 --- a/api/src/main/java/io/minio/messages/SelectObjectContentRequestBase.java +++ b/api/src/main/java/io/minio/messages/BaseSelectParameters.java @@ -20,9 +20,11 @@ import javax.annotation.Nonnull; import org.simpleframework.xml.Element; -/** Base class for {@link SelectObjectContentRequest} and {@link SelectParameters}. */ +/** + * Base class for {@link SelectObjectContentRequest} and {@link RestoreRequest.SelectParameters}. + */ @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") -public abstract class SelectObjectContentRequestBase { +public abstract class BaseSelectParameters { @Element(name = "Expression") private String expression; @@ -35,7 +37,7 @@ public abstract class SelectObjectContentRequestBase { @Element(name = "OutputSerialization") private OutputSerialization outputSerialization; - public SelectObjectContentRequestBase( + public BaseSelectParameters( @Nonnull String expression, @Nonnull InputSerialization is, @Nonnull OutputSerialization os) { this.expression = Objects.requireNonNull(expression, "Expression must not be null"); this.inputSerialization = Objects.requireNonNull(is, "InputSerialization must not be null"); diff --git a/api/src/main/java/io/minio/messages/Bucket.java b/api/src/main/java/io/minio/messages/Bucket.java deleted file mode 100644 index bd4d150ec..000000000 --- a/api/src/main/java/io/minio/messages/Bucket.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2015 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 java.time.ZonedDateTime; -import org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** Helper class to denote bucket information for {@link ListAllMyBucketsResult}. */ -@Root(name = "Bucket", strict = false) -public class Bucket { - @Element(name = "Name") - private String name; - - @Element(name = "CreationDate") - private ResponseDate creationDate; - - @Element(name = "BucketRegion", required = false) - private String bucketRegion; - - public Bucket() {} - - /** Returns bucket name. */ - public String name() { - return name; - } - - /** Returns creation date. */ - public ZonedDateTime creationDate() { - return creationDate.zonedDateTime(); - } - - public String bucketRegion() { - return bucketRegion; - } -} diff --git a/api/src/main/java/io/minio/messages/BucketMetadata.java b/api/src/main/java/io/minio/messages/BucketMetadata.java deleted file mode 100644 index 91be4e8de..000000000 --- a/api/src/main/java/io/minio/messages/BucketMetadata.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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.messages; - -import com.fasterxml.jackson.annotation.JsonProperty; - -/** Helper class to denote bucket information for {@link EventMetadata}. */ -@edu.umd.cs.findbugs.annotations.SuppressFBWarnings( - value = "UwF", - justification = "Everything in this class is initialized by JSON unmarshalling.") -public class BucketMetadata { - @JsonProperty private String name; - @JsonProperty private Identity ownerIdentity; - @JsonProperty private String arn; - - public String name() { - return name; - } - - public String owner() { - if (ownerIdentity == null) { - return null; - } - - return ownerIdentity.principalId(); - } - - public String arn() { - return arn; - } -} diff --git a/api/src/main/java/io/minio/messages/CORSConfiguration.java b/api/src/main/java/io/minio/messages/CORSConfiguration.java index aedc3d253..1b5b77bd6 100644 --- a/api/src/main/java/io/minio/messages/CORSConfiguration.java +++ b/api/src/main/java/io/minio/messages/CORSConfiguration.java @@ -46,6 +46,11 @@ public List rules() { return Utils.unmodifiableList(rules); } + @Override + public String toString() { + return String.format("CORSConfiguration{rules=%s}", Utils.stringify(rules)); + } + public static class CORSRule { @ElementList(entry = "AllowedHeader", inline = true, required = false) private List allowedHeaders; @@ -107,5 +112,18 @@ public String id() { public Integer maxAgeSeconds() { return maxAgeSeconds; } + + @Override + public String toString() { + return String.format( + "CORSRule{allowedHeaders=%s, allowedMethods=%s, allowedOrigins=%s, exposeHeaders=%s, " + + "id=%s, maxAgeSeconds=%s}", + Utils.stringify(allowedHeaders), + Utils.stringify(allowedMethods), + Utils.stringify(allowedOrigins), + Utils.stringify(exposeHeaders), + Utils.stringify(id), + Utils.stringify(maxAgeSeconds)); + } } } diff --git a/api/src/main/java/io/minio/messages/CannedAcl.java b/api/src/main/java/io/minio/messages/CannedAcl.java deleted file mode 100644 index dc067d34a..000000000 --- a/api/src/main/java/io/minio/messages/CannedAcl.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2021 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.fasterxml.jackson.annotation.JsonCreator; -import org.simpleframework.xml.Root; -import org.simpleframework.xml.convert.Convert; -import org.simpleframework.xml.convert.Converter; -import org.simpleframework.xml.stream.InputNode; -import org.simpleframework.xml.stream.OutputNode; - -/** CannedAcl representing retrieval cannedAcl value. */ -@Root(name = "CannedAcl") -@Convert(CannedAcl.CannedAclConverter.class) -public enum CannedAcl { - PRIVATE("private"), - PUBLIC_READ("public-read"), - PUBLIC_READ_WRITE("public-read-write"), - AUTHENTICATED_READ("authenticated-read"), - AWS_EXEC_READ("aws-exec-read"), - BUCKET_OWNER_READ("bucket-owner-read"), - BUCKET_OWNER_FULL_CONTROL("bucket-owner-full-control"); - - private final String value; - - private CannedAcl(String value) { - this.value = value; - } - - public String toString() { - return this.value; - } - - /** Returns CannedAcl of given string. */ - @JsonCreator - public static CannedAcl fromString(String cannedAclString) { - for (CannedAcl cannedAcl : CannedAcl.values()) { - if (cannedAclString.equals(cannedAcl.value)) { - return cannedAcl; - } - } - - throw new IllegalArgumentException("Unknown canned ACL '" + cannedAclString + "'"); - } - - /** XML converter class. */ - public static class CannedAclConverter implements Converter { - @Override - public CannedAcl read(InputNode node) throws Exception { - return CannedAcl.fromString(node.getValue()); - } - - @Override - public void write(OutputNode node, CannedAcl cannedAcl) throws Exception { - node.setValue(cannedAcl.toString()); - } - } -} diff --git a/api/src/main/java/io/minio/messages/Checksum.java b/api/src/main/java/io/minio/messages/Checksum.java index 2263fa138..835ea1e79 100644 --- a/api/src/main/java/io/minio/messages/Checksum.java +++ b/api/src/main/java/io/minio/messages/Checksum.java @@ -18,6 +18,7 @@ import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; +import io.minio.Utils; import java.util.Locale; import org.simpleframework.xml.Element; import org.simpleframework.xml.Root; @@ -45,6 +46,21 @@ public class Checksum { public Checksum() {} + public Checksum( + String checksumCRC32, + String checksumCRC32C, + String checksumCRC64NVME, + String checksumSHA1, + String checksumSHA256, + String checksumType) { + this.checksumCRC32 = checksumCRC32; + this.checksumCRC32C = checksumCRC32C; + this.checksumCRC64NVME = checksumCRC64NVME; + this.checksumSHA1 = checksumSHA1; + this.checksumSHA256 = checksumSHA256; + this.checksumType = checksumType; + } + public String checksumCRC32() { return checksumCRC32; } @@ -85,4 +101,21 @@ public Multimap headers() { addHeader(map, "SHA256", checksumSHA256); return map; } + + protected String stringify() { + return String.format( + "checksumCRC32=%s, checksumCRC32C=%s, checksumCRC64NVME=%s, checksumSHA1=%s," + + " checksumSHA256=%s, checksumType=%s", + Utils.stringify(checksumCRC32), + Utils.stringify(checksumCRC32C), + Utils.stringify(checksumCRC64NVME), + Utils.stringify(checksumSHA1), + Utils.stringify(checksumSHA256), + Utils.stringify(checksumType)); + } + + @Override + public String toString() { + return String.format("Checksum{%s}", stringify()); + } } diff --git a/api/src/main/java/io/minio/messages/CloudFunctionConfiguration.java b/api/src/main/java/io/minio/messages/CloudFunctionConfiguration.java deleted file mode 100644 index 013a28a2b..000000000 --- a/api/src/main/java/io/minio/messages/CloudFunctionConfiguration.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2017 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 org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** Helper class to denote CloudFunction configuration of {@link NotificationConfiguration}. */ -@Root(name = "CloudFunctionConfiguration", strict = false) -public class CloudFunctionConfiguration extends NotificationCommonConfiguration { - @Element(name = "CloudFunction") - private String cloudFunction; - - public CloudFunctionConfiguration() { - super(); - } - - /** Returns cloudFunction. */ - public String cloudFunction() { - return cloudFunction; - } - - /** Sets cloudFunction. */ - public void setCloudFunction(String cloudFunction) { - this.cloudFunction = cloudFunction; - } -} diff --git a/api/src/main/java/io/minio/messages/CompleteMultipartUpload.java b/api/src/main/java/io/minio/messages/CompleteMultipartUpload.java index 94a9b91dc..66caf3ff7 100644 --- a/api/src/main/java/io/minio/messages/CompleteMultipartUpload.java +++ b/api/src/main/java/io/minio/messages/CompleteMultipartUpload.java @@ -35,13 +35,13 @@ @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") public class CompleteMultipartUpload { @ElementList(name = "Part", inline = true) - private List partList; + private List parts; /** Constucts a new CompleteMultipartUpload object with given parts. */ public CompleteMultipartUpload(@Nonnull Part[] parts) throws IllegalArgumentException { if (Objects.requireNonNull(parts, "parts must not be null").length == 0) { throw new IllegalArgumentException("parts cannot be empty"); } - this.partList = Utils.unmodifiableList(Arrays.asList(parts)); + this.parts = Utils.unmodifiableList(Arrays.asList(parts)); } } diff --git a/api/src/main/java/io/minio/messages/CompleteMultipartUploadOutput.java b/api/src/main/java/io/minio/messages/CompleteMultipartUploadOutput.java deleted file mode 100644 index 78d504a79..000000000 --- a/api/src/main/java/io/minio/messages/CompleteMultipartUploadOutput.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2015 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 org.simpleframework.xml.Namespace; -import org.simpleframework.xml.Root; - -/** - * Object representation of response XML of CompleteMultipartUpload - * API. - */ -@Root(name = "CompleteMultipartUploadOutput") -@Namespace(reference = "http://s3.amazonaws.com/doc/2006-03-01/") -public class CompleteMultipartUploadOutput extends CompleteMultipartUploadResult {} diff --git a/api/src/main/java/io/minio/messages/CompleteMultipartUploadResult.java b/api/src/main/java/io/minio/messages/CompleteMultipartUploadResult.java index fed1a0901..151f3463d 100644 --- a/api/src/main/java/io/minio/messages/CompleteMultipartUploadResult.java +++ b/api/src/main/java/io/minio/messages/CompleteMultipartUploadResult.java @@ -16,6 +16,7 @@ package io.minio.messages; +import io.minio.Utils; import org.simpleframework.xml.Element; import org.simpleframework.xml.Namespace; import org.simpleframework.xml.Root; @@ -27,7 +28,7 @@ */ @Root(name = "CompleteMultipartUploadResult") @Namespace(reference = "http://s3.amazonaws.com/doc/2006-03-01/") -public class CompleteMultipartUploadResult { +public class CompleteMultipartUploadResult extends Checksum { @Element(name = "Location") private String location; @@ -40,24 +41,6 @@ public class CompleteMultipartUploadResult { @Element(name = "ETag") private String etag; - @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 CompleteMultipartUploadResult() {} public String location() { @@ -76,27 +59,14 @@ public String etag() { return etag; } - 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; + @Override + public String toString() { + return String.format( + "CompleteMultipartUploadResult{location=%s, bucket=%s, object=%s, etag=%s, %s}", + Utils.stringify(location), + Utils.stringify(bucket), + Utils.stringify(object), + Utils.stringify(etag), + super.stringify()); } } diff --git a/api/src/main/java/io/minio/messages/CompressionType.java b/api/src/main/java/io/minio/messages/CompressionType.java deleted file mode 100644 index 7b6424cfc..000000000 --- a/api/src/main/java/io/minio/messages/CompressionType.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2019 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; - -/** CSV/JSON object's compression format for select object content. */ -public enum CompressionType { - NONE, - GZIP, - BZIP2; -} diff --git a/api/src/main/java/io/minio/messages/Contents.java b/api/src/main/java/io/minio/messages/Contents.java index 873e800e7..858f6f7eb 100644 --- a/api/src/main/java/io/minio/messages/Contents.java +++ b/api/src/main/java/io/minio/messages/Contents.java @@ -31,4 +31,9 @@ public Contents() { public Contents(String prefix) { super(prefix); } + + @Override + public String toString() { + return String.format("Contents{%s}", super.toString()); + } } diff --git a/api/src/main/java/io/minio/messages/CopyObjectResult.java b/api/src/main/java/io/minio/messages/CopyObjectResult.java index 628484b9a..4ad5a88e1 100644 --- a/api/src/main/java/io/minio/messages/CopyObjectResult.java +++ b/api/src/main/java/io/minio/messages/CopyObjectResult.java @@ -17,6 +17,8 @@ package io.minio.messages; +import io.minio.Time; +import io.minio.Utils; import java.time.ZonedDateTime; import org.simpleframework.xml.Element; import org.simpleframework.xml.Namespace; @@ -28,30 +30,12 @@ */ @Root(name = "CopyObjectResult", strict = false) @Namespace(reference = "http://s3.amazonaws.com/doc/2006-03-01/") -public class CopyObjectResult { +public class CopyObjectResult extends Checksum { @Element(name = "ETag") private String etag; @Element(name = "LastModified") - private ResponseDate lastModified; - - @Element(name = "ChecksumType", required = false) - private String checksumType; - - @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; + private Time.S3Time lastModified; public CopyObjectResult() {} @@ -62,30 +46,17 @@ public String etag() { /** Returns last modified time. */ public ZonedDateTime lastModified() { - return lastModified.zonedDateTime(); - } - - public String checksumType() { - return checksumType; - } - - public String checksumCRC32() { - return checksumCRC32; - } - - public String checksumCRC32C() { - return checksumCRC32C; - } - - public String checksumCRC64NVME() { - return checksumCRC64NVME; + return lastModified == null ? null : lastModified.toZonedDateTime(); } - public String checksumSHA1() { - return checksumSHA1; + protected String stringify() { + return String.format( + "etag=%s, lastModified=%s, %s", + Utils.stringify(etag), Utils.stringify(lastModified), super.stringify()); } - public String checksumSHA256() { - return checksumSHA256; + @Override + public String toString() { + return String.format("CopyObjectResult{%s}", stringify()); } } diff --git a/api/src/main/java/io/minio/messages/CopyPartResult.java b/api/src/main/java/io/minio/messages/CopyPartResult.java index 6c6ad3c80..52635b48a 100644 --- a/api/src/main/java/io/minio/messages/CopyPartResult.java +++ b/api/src/main/java/io/minio/messages/CopyPartResult.java @@ -27,4 +27,9 @@ */ @Root(name = "CopyPartResult", strict = false) @Namespace(reference = "http://s3.amazonaws.com/doc/2006-03-01/") -public class CopyPartResult extends CopyObjectResult {} +public class CopyPartResult extends CopyObjectResult { + @Override + public String toString() { + return String.format("CopyPartResult{%s}", super.stringify()); + } +} diff --git a/api/src/main/java/io/minio/messages/CreateBucketConfiguration.java b/api/src/main/java/io/minio/messages/CreateBucketConfiguration.java index 415498b5c..842dfe8a4 100644 --- a/api/src/main/java/io/minio/messages/CreateBucketConfiguration.java +++ b/api/src/main/java/io/minio/messages/CreateBucketConfiguration.java @@ -21,7 +21,7 @@ import org.simpleframework.xml.Root; /** - * Object representation of response XML of CreateBucket * API. */ diff --git a/api/src/main/java/io/minio/messages/CsvInputSerialization.java b/api/src/main/java/io/minio/messages/CsvInputSerialization.java deleted file mode 100644 index 843bf57c8..000000000 --- a/api/src/main/java/io/minio/messages/CsvInputSerialization.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2019 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 org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** - * Helper class to denote CSV input serialization request XML as per {@link - * SelectObjectContentRequest}. - */ -@Root(name = "CSV") -@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") -public class CsvInputSerialization { - @Element(name = "AllowQuotedRecordDelimiter", required = false) - private boolean allowQuotedRecordDelimiter; - - @Element(name = "Comments", required = false) - private Character comments; - - @Element(name = "FieldDelimiter", required = false) - private Character fieldDelimiter; - - @Element(name = "FileHeaderInfo", required = false) - private FileHeaderInfo fileHeaderInfo; - - @Element(name = "QuoteCharacter", required = false) - private Character quoteCharacter; - - @Element(name = "QuoteEscapeCharacter", required = false) - private Character quoteEscapeCharacter; - - @Element(name = "RecordDelimiter", required = false) - private Character recordDelimiter; - - /** Constructs a new CsvInputSerialization object. */ - public CsvInputSerialization( - boolean allowQuotedRecordDelimiter, - Character comments, - Character fieldDelimiter, - FileHeaderInfo fileHeaderInfo, - Character quoteCharacter, - Character quoteEscapeCharacter, - Character recordDelimiter) { - this.allowQuotedRecordDelimiter = allowQuotedRecordDelimiter; - this.comments = comments; - this.fieldDelimiter = fieldDelimiter; - this.fileHeaderInfo = fileHeaderInfo; - this.quoteCharacter = quoteCharacter; - this.quoteEscapeCharacter = quoteEscapeCharacter; - this.recordDelimiter = recordDelimiter; - } -} diff --git a/api/src/main/java/io/minio/messages/CsvOutputSerialization.java b/api/src/main/java/io/minio/messages/CsvOutputSerialization.java deleted file mode 100644 index 40a46a3bc..000000000 --- a/api/src/main/java/io/minio/messages/CsvOutputSerialization.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2019 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 org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** - * Helper class to denote CSV output serialization request XML as per {@link - * SelectObjectContentRequest}. - */ -@Root(name = "CSV") -@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") -public class CsvOutputSerialization { - @Element(name = "FieldDelimiter", required = false) - private Character fieldDelimiter; - - @Element(name = "QuoteCharacter", required = false) - private Character quoteCharacter; - - @Element(name = "QuoteEscapeCharacter", required = false) - private Character quoteEscapeCharacter; - - @Element(name = "QuoteFields", required = false) - private QuoteFields quoteFields; - - @Element(name = "RecordDelimiter", required = false) - private Character recordDelimiter; - - /** Constructs a new CsvOutputSerialization object. */ - public CsvOutputSerialization( - Character fieldDelimiter, - Character quoteCharacter, - Character quoteEscapeCharacter, - QuoteFields quoteFields, - Character recordDelimiter) { - this.fieldDelimiter = fieldDelimiter; - this.quoteCharacter = quoteCharacter; - this.quoteEscapeCharacter = quoteEscapeCharacter; - this.quoteFields = quoteFields; - this.recordDelimiter = recordDelimiter; - } -} diff --git a/api/src/main/java/io/minio/messages/DateDays.java b/api/src/main/java/io/minio/messages/DateDays.java deleted file mode 100644 index 03eda56ad..000000000 --- a/api/src/main/java/io/minio/messages/DateDays.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.messages; - -import java.time.ZonedDateTime; -import org.simpleframework.xml.Element; - -/** Base class for {@link Transition} and {@link Expiration}. */ -public abstract class DateDays { - @Element(name = "Date", required = false) - protected ResponseDate date; - - @Element(name = "Days", required = false) - protected Integer days; - - public ZonedDateTime date() { - return (date != null) ? date.zonedDateTime() : null; - } - - public Integer days() { - return days; - } -} diff --git a/api/src/main/java/io/minio/messages/DeleteError.java b/api/src/main/java/io/minio/messages/DeleteError.java deleted file mode 100644 index c4c9a51ba..000000000 --- a/api/src/main/java/io/minio/messages/DeleteError.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2017 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 org.simpleframework.xml.Namespace; -import org.simpleframework.xml.Root; - -/** Helper class to denote error for {@link DeleteResult}. */ -@Root(name = "Error", strict = false) -@Namespace(reference = "http://s3.amazonaws.com/doc/2006-03-01/") -public class DeleteError extends ErrorResponse { - private static final long serialVersionUID = 1905162041950251407L; // fix SE_BAD_FIELD -} diff --git a/api/src/main/java/io/minio/messages/DeleteMarker.java b/api/src/main/java/io/minio/messages/DeleteMarker.java deleted file mode 100644 index 0e95b74ed..000000000 --- a/api/src/main/java/io/minio/messages/DeleteMarker.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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.messages; - -import org.simpleframework.xml.Root; - -/** Helper class to denote delete marker information in {@link ListVersionsResult}. */ -@Root(name = "DeleteMarker", strict = false) -public class DeleteMarker extends Item { - public DeleteMarker() { - super(); - } - - public DeleteMarker(String prefix) { - super(prefix); - } -} diff --git a/api/src/main/java/io/minio/messages/DeleteMarkerReplication.java b/api/src/main/java/io/minio/messages/DeleteMarkerReplication.java deleted file mode 100644 index 9141fc2fa..000000000 --- a/api/src/main/java/io/minio/messages/DeleteMarkerReplication.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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.messages; - -import javax.annotation.Nullable; -import org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** Helper class to denote delete marker replication information for {@link ReplicationRule}. */ -@Root(name = "DeleteMarkerReplication") -public class DeleteMarkerReplication { - @Element(name = "Status", required = false) - private Status status; - - /** Constructs new server-side encryption configuration rule. */ - public DeleteMarkerReplication( - @Nullable @Element(name = "Status", required = false) Status status) { - this.status = (status == null) ? Status.DISABLED : status; - } - - public Status status() { - return status; - } -} diff --git a/api/src/main/java/io/minio/messages/DeleteObject.java b/api/src/main/java/io/minio/messages/DeleteObject.java deleted file mode 100644 index 136a9a6b1..000000000 --- a/api/src/main/java/io/minio/messages/DeleteObject.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2017 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.Time; -import java.time.ZonedDateTime; -import org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; -import org.simpleframework.xml.convert.Convert; -import org.simpleframework.xml.convert.Converter; -import org.simpleframework.xml.stream.InputNode; -import org.simpleframework.xml.stream.OutputNode; - -/** Helper class to denote Object information for {@link DeleteRequest}. */ -@Root(name = "Object") -@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") -public class DeleteObject { - @Element(name = "Key") - private String name; - - @Element(name = "VersionId", required = false) - private String versionId; - - @Element(name = "ETag", required = false) - private String etag; - - @Element(name = "LastModifiedTime", required = false) - private HttpHeaderDate lastModifiedTime; - - @Element(name = "Size", required = false) - private Long size; - - public DeleteObject(String name) { - this.name = name; - } - - public DeleteObject(String name, String versionId) { - this(name); - this.versionId = versionId; - } - - public DeleteObject( - String name, String versionId, String etag, ZonedDateTime lastModifiedTime, Long size) { - this(name, versionId); - this.etag = etag; - this.lastModifiedTime = lastModifiedTime == null ? null : new HttpHeaderDate(lastModifiedTime); - this.size = size; - } - - /** HTTP header date wrapping {@link ZonedDateTime}. */ - @Root - @Convert(HttpHeaderDate.HttpHeaderDateConverter.class) - public static class HttpHeaderDate { - private ZonedDateTime zonedDateTime; - - public HttpHeaderDate(ZonedDateTime zonedDateTime) { - this.zonedDateTime = zonedDateTime; - } - - public String toString() { - return zonedDateTime.format(Time.HTTP_HEADER_DATE_FORMAT); - } - - public static HttpHeaderDate fromString(String dateString) { - return new HttpHeaderDate(ZonedDateTime.parse(dateString, Time.HTTP_HEADER_DATE_FORMAT)); - } - - /** XML converter class. */ - public static class HttpHeaderDateConverter implements Converter { - @Override - public HttpHeaderDate read(InputNode node) throws Exception { - return HttpHeaderDate.fromString(node.getValue()); - } - - @Override - public void write(OutputNode node, HttpHeaderDate date) { - node.setValue(date.toString()); - } - } - } -} diff --git a/api/src/main/java/io/minio/messages/DeleteReplication.java b/api/src/main/java/io/minio/messages/DeleteReplication.java deleted file mode 100644 index c24ce7437..000000000 --- a/api/src/main/java/io/minio/messages/DeleteReplication.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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.messages; - -import javax.annotation.Nullable; -import org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** - * Helper class to denote delete replication information for {@link ReplicationRule}. This is MinIO - * specific extension. - */ -@Root(name = "DeleteReplication") -public class DeleteReplication { - @Element(name = "Status", required = false) - private Status status; - - public DeleteReplication(@Nullable @Element(name = "Status", required = false) Status status) { - this.status = (status == null) ? Status.DISABLED : status; - } - - public Status status() { - return status; - } -} diff --git a/api/src/main/java/io/minio/messages/DeleteRequest.java b/api/src/main/java/io/minio/messages/DeleteRequest.java index eef72d46a..e6e48f3bc 100644 --- a/api/src/main/java/io/minio/messages/DeleteRequest.java +++ b/api/src/main/java/io/minio/messages/DeleteRequest.java @@ -16,7 +16,9 @@ package io.minio.messages; +import io.minio.Time; import io.minio.Utils; +import java.time.ZonedDateTime; import java.util.List; import java.util.Objects; import javax.annotation.Nonnull; @@ -24,6 +26,10 @@ import org.simpleframework.xml.ElementList; import org.simpleframework.xml.Namespace; import org.simpleframework.xml.Root; +import org.simpleframework.xml.convert.Convert; +import org.simpleframework.xml.convert.Converter; +import org.simpleframework.xml.stream.InputNode; +import org.simpleframework.xml.stream.OutputNode; /** * Object representation of request XML of objectList; + private List objects; /** Constructs new delete request for given object list and quiet flag. */ - public DeleteRequest(@Nonnull List objectList, boolean quiet) { - this.objectList = - Utils.unmodifiableList(Objects.requireNonNull(objectList, "Object list must not be null")); + public DeleteRequest(@Nonnull List objects, boolean quiet) { + this.objects = + Utils.unmodifiableList(Objects.requireNonNull(objects, "Object list must not be null")); this.quiet = quiet; } + + @Root(name = "Object") + @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") + public static class Object { + @Element(name = "Key") + private String name; + + @Element(name = "VersionId", required = false) + private String versionId; + + @Element(name = "ETag", required = false) + private String etag; + + @Element(name = "LastModifiedTime", required = false) + private HttpHeaderDate lastModifiedTime; + + @Element(name = "Size", required = false) + private Long size; + + public Object(String name) { + this.name = name; + } + + public Object(String name, String versionId) { + this(name); + this.versionId = versionId; + } + + public Object( + String name, String versionId, String etag, ZonedDateTime lastModifiedTime, Long size) { + this(name, versionId); + this.etag = etag; + this.lastModifiedTime = + lastModifiedTime == null ? null : new HttpHeaderDate(lastModifiedTime); + this.size = size; + } + + /** HTTP header date wrapping {@link ZonedDateTime}. */ + @Root + @Convert(HttpHeaderDate.HttpHeaderDateConverter.class) + public static class HttpHeaderDate { + private ZonedDateTime zonedDateTime; + + public HttpHeaderDate(ZonedDateTime zonedDateTime) { + this.zonedDateTime = zonedDateTime; + } + + public String toString() { + return zonedDateTime.format(Time.HTTP_HEADER_DATE_FORMAT); + } + + public static HttpHeaderDate fromString(String dateString) { + return new HttpHeaderDate(ZonedDateTime.parse(dateString, Time.HTTP_HEADER_DATE_FORMAT)); + } + + /** XML converter class. */ + public static class HttpHeaderDateConverter implements Converter { + @Override + public HttpHeaderDate read(InputNode node) throws Exception { + return HttpHeaderDate.fromString(node.getValue()); + } + + @Override + public void write(OutputNode node, HttpHeaderDate date) { + node.setValue(date.toString()); + } + } + } + } } diff --git a/api/src/main/java/io/minio/messages/DeleteResult.java b/api/src/main/java/io/minio/messages/DeleteResult.java index c5c1a04e1..9bb051963 100644 --- a/api/src/main/java/io/minio/messages/DeleteResult.java +++ b/api/src/main/java/io/minio/messages/DeleteResult.java @@ -19,6 +19,7 @@ import io.minio.Utils; import java.util.LinkedList; import java.util.List; +import org.simpleframework.xml.Element; import org.simpleframework.xml.ElementList; import org.simpleframework.xml.Namespace; import org.simpleframework.xml.Root; @@ -32,26 +33,86 @@ @Namespace(reference = "http://s3.amazonaws.com/doc/2006-03-01/") public class DeleteResult { @ElementList(name = "Deleted", inline = true, required = false) - private List objectList; + private List objects; @ElementList(name = "Error", inline = true, required = false) - private List errorList; + private List errors; public DeleteResult() {} /** Constructs new delete result with an error. */ - public DeleteResult(DeleteError error) { - this.errorList = new LinkedList(); - this.errorList.add(error); + public DeleteResult(Error error) { + this.errors = new LinkedList(); + this.errors.add(error); } /** Returns deleted object list. */ - public List objectList() { - return Utils.unmodifiableList(objectList); + public List objects() { + return Utils.unmodifiableList(objects); } /** Returns delete error list. */ - public List errorList() { - return Utils.unmodifiableList(errorList); + public List errors() { + return Utils.unmodifiableList(errors); + } + + @Override + public String toString() { + return String.format( + "DeleteResult{objects=%s, errors=%s}", Utils.stringify(objects), Utils.stringify(errors)); + } + + @Root(name = "Deleted", strict = false) + public static class Deleted { + @Element(name = "Key") + private String name; + + @Element(name = "VersionId", required = false) + private String versionId; + + @Element(name = "DeleteMarker", required = false) + private boolean deleteMarker; + + @Element(name = "DeleteMarkerVersionId", required = false) + private String deleteMarkerVersionId; + + public Deleted() {} + + public String name() { + return name; + } + + public String versionId() { + return versionId; + } + + public boolean deleteMarker() { + return deleteMarker; + } + + public String deleteMarkerVersionId() { + return deleteMarkerVersionId; + } + + @Override + public String toString() { + return String.format( + "Deleted{name=%s, versionId=%s, deleteMarker=%s, deleteMarkerVersionId=%s}", + Utils.stringify(name), + Utils.stringify(versionId), + Utils.stringify(deleteMarker), + Utils.stringify(deleteMarkerVersionId)); + } + } + + @Root(name = "Error", strict = false) + @Namespace(reference = "http://s3.amazonaws.com/doc/2006-03-01/") + public static class Error extends ErrorResponse { + private static final long serialVersionUID = 1905162041950251407L; // fix SE_BAD_FIELD + + @Override + public String toString() { + return String.format("Error{%s}", super.stringify()); + } } } diff --git a/api/src/main/java/io/minio/messages/DeletedObject.java b/api/src/main/java/io/minio/messages/DeletedObject.java deleted file mode 100644 index 8338909e5..000000000 --- a/api/src/main/java/io/minio/messages/DeletedObject.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2017 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 org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** Helper class to denote deleted object for {@link DeleteResult}. */ -@Root(name = "Deleted", strict = false) -public class DeletedObject { - @Element(name = "Key") - private String name; - - @Element(name = "VersionId", required = false) - private String versionId; - - @Element(name = "DeleteMarker", required = false) - private boolean deleteMarker; - - @Element(name = "DeleteMarkerVersionId", required = false) - private String deleteMarkerVersionId; - - public DeletedObject() {} - - public String name() { - return name; - } - - public String versionId() { - return versionId; - } - - public boolean deleteMarker() { - return deleteMarker; - } - - public String deleteMarkerVersionId() { - return deleteMarkerVersionId; - } -} diff --git a/api/src/main/java/io/minio/messages/Encryption.java b/api/src/main/java/io/minio/messages/Encryption.java deleted file mode 100644 index b37f34c9c..000000000 --- a/api/src/main/java/io/minio/messages/Encryption.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2021 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 java.util.Objects; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** Helper class to denote encryption information of {@link S3OutputLocation}. */ -@Root(name = "Encryption") -@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") -public class Encryption { - @Element(name = "EncryptionType") - private SseAlgorithm encryptionType; - - @Element(name = "KMSContext", required = false) - private String kmsContext; - - @Element(name = "KMSKeyId", required = false) - private String kmsKeyId; - - public Encryption( - @Nonnull SseAlgorithm encryptionType, - @Nullable String kmsContext, - @Nullable String kmsKeyId) { - this.encryptionType = - Objects.requireNonNull(encryptionType, "Encryption type must not be null"); - this.kmsContext = kmsContext; - this.kmsKeyId = kmsKeyId; - } -} diff --git a/api/src/main/java/io/minio/messages/EncryptionConfiguration.java b/api/src/main/java/io/minio/messages/EncryptionConfiguration.java deleted file mode 100644 index fb641c5a9..000000000 --- a/api/src/main/java/io/minio/messages/EncryptionConfiguration.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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.messages; - -import javax.annotation.Nullable; -import org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** - * Helper class to denote encryption configuration information for {@link ReplicationDestination}. - */ -@Root(name = "EncryptionConfiguration") -public class EncryptionConfiguration { - @Element(name = "ReplicaKmsKeyID", required = false) - private String replicaKmsKeyID; - - public EncryptionConfiguration( - @Nullable @Element(name = "ReplicaKmsKeyID", required = false) String replicaKmsKeyID) { - this.replicaKmsKeyID = replicaKmsKeyID; - } - - public String replicaKmsKeyID() { - return this.replicaKmsKeyID; - } -} diff --git a/api/src/main/java/io/minio/messages/ErrorResponse.java b/api/src/main/java/io/minio/messages/ErrorResponse.java index 5e45f2bfb..b7a3d8f8b 100644 --- a/api/src/main/java/io/minio/messages/ErrorResponse.java +++ b/api/src/main/java/io/minio/messages/ErrorResponse.java @@ -16,6 +16,7 @@ package io.minio.messages; +import io.minio.Utils; import java.io.Serializable; import org.simpleframework.xml.Element; import org.simpleframework.xml.Namespace; @@ -106,28 +107,21 @@ public String resource() { return resource; } - /** Returns string representation of this object. */ + protected String stringify() { + return String.format( + "code=%s, message=%s, bucketName=%s, objectName=%s, resource=%s," + + " requestId=%s, hostId=%s", + Utils.stringify(code), + Utils.stringify(message), + Utils.stringify(bucketName), + Utils.stringify(objectName), + Utils.stringify(resource), + Utils.stringify(requestId), + Utils.stringify(hostId)); + } + + @Override public String toString() { - return "ErrorResponse(code = " - + code - + ", " - + "message = " - + message - + ", " - + "bucketName = " - + bucketName - + ", " - + "objectName = " - + objectName - + ", " - + "resource = " - + resource - + ", " - + "requestId = " - + requestId - + ", " - + "hostId = " - + hostId - + ")"; + return String.format("ErrorResponse{%s}", stringify()); } } diff --git a/api/src/main/java/io/minio/messages/Event.java b/api/src/main/java/io/minio/messages/Event.java deleted file mode 100644 index 7e59e22aa..000000000 --- a/api/src/main/java/io/minio/messages/Event.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, - * (C) 2018 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.fasterxml.jackson.annotation.JsonProperty; -import io.minio.Utils; -import java.time.ZonedDateTime; -import java.util.Map; - -/** Helper class to denote single event record for {@link NotificationRecords}. */ -@edu.umd.cs.findbugs.annotations.SuppressFBWarnings( - value = "UuF", - justification = "eventVersion and eventSource are available for completeness") -public class Event { - @JsonProperty private String eventVersion; - @JsonProperty private String eventSource; - @JsonProperty private String awsRegion; - @JsonProperty private EventType eventName; - @JsonProperty private Identity userIdentity; - @JsonProperty private Map requestParameters; - @JsonProperty private Map responseElements; - @JsonProperty private EventMetadata s3; - @JsonProperty private Source source; - @JsonProperty private ResponseDate eventTime; - - public String region() { - return awsRegion; - } - - public ZonedDateTime eventTime() { - return eventTime.zonedDateTime(); - } - - public EventType eventType() { - return eventName; - } - - public String userId() { - if (userIdentity == null) { - return null; - } - - return userIdentity.principalId(); - } - - public Map requestParameters() { - return Utils.unmodifiableMap(requestParameters); - } - - public Map responseElements() { - return Utils.unmodifiableMap(responseElements); - } - - public String bucketName() { - if (s3 == null) { - return null; - } - - return s3.bucketName(); - } - - public String bucketOwner() { - if (s3 == null) { - return null; - } - - return s3.bucketOwner(); - } - - public String bucketArn() { - if (s3 == null) { - return null; - } - - return s3.bucketArn(); - } - - public String objectName() { - if (s3 == null) { - return null; - } - - return s3.objectName(); - } - - public long objectSize() { - if (s3 == null) { - return -1; - } - - return s3.objectSize(); - } - - public String etag() { - if (s3 == null) { - return null; - } - - return s3.etag(); - } - - public String objectVersionId() { - if (s3 == null) { - return null; - } - - return s3.objectVersionId(); - } - - public String sequencer() { - if (s3 == null) { - return null; - } - - return s3.sequencer(); - } - - public Map userMetadata() { - if (s3 == null) { - return null; - } - - return s3.userMetadata(); - } - - public String host() { - if (source == null) { - return null; - } - - return source.host(); - } - - public String port() { - if (source == null) { - return null; - } - - return source.port(); - } - - public String userAgent() { - if (source == null) { - return null; - } - - return source.userAgent(); - } -} diff --git a/api/src/main/java/io/minio/messages/EventMetadata.java b/api/src/main/java/io/minio/messages/EventMetadata.java deleted file mode 100644 index 35811ca05..000000000 --- a/api/src/main/java/io/minio/messages/EventMetadata.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * 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.messages; - -import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.Map; - -/** Helper class to denote event metadata for {@link EventMetadata}. */ -@edu.umd.cs.findbugs.annotations.SuppressFBWarnings( - value = {"UwF", "UuF"}, - justification = - "Everything in this class is initialized by JSON unmarshalling " - + "and s3SchemaVersion/configurationId are available for completeness.") -public class EventMetadata { - @JsonProperty private String s3SchemaVersion; - @JsonProperty private String configurationId; - @JsonProperty private BucketMetadata bucket; - @JsonProperty private ObjectMetadata object; - - public String bucketName() { - if (bucket == null) { - return null; - } - - return bucket.name(); - } - - public String bucketOwner() { - if (bucket == null) { - return null; - } - - return bucket.owner(); - } - - public String bucketArn() { - if (bucket == null) { - return null; - } - - return bucket.arn(); - } - - public String objectName() { - if (object == null) { - return null; - } - - return object.key(); - } - - public long objectSize() { - if (object == null) { - return -1; - } - - return object.size(); - } - - public String etag() { - if (object == null) { - return null; - } - - return object.etag(); - } - - public String objectVersionId() { - if (object == null) { - return null; - } - - return object.versionId(); - } - - public String sequencer() { - if (object == null) { - return null; - } - - return object.sequencer(); - } - - public Map userMetadata() { - if (object == null) { - return null; - } - - return object.userMetadata(); - } -} diff --git a/api/src/main/java/io/minio/messages/ExistingObjectReplication.java b/api/src/main/java/io/minio/messages/ExistingObjectReplication.java deleted file mode 100644 index d4b4aa01f..000000000 --- a/api/src/main/java/io/minio/messages/ExistingObjectReplication.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.messages; - -import java.util.Objects; -import javax.annotation.Nonnull; -import org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** Helper class to denote existing object replication information for {@link ReplicationRule}. */ -@Root(name = "ExistingObjectReplication") -public class ExistingObjectReplication { - @Element(name = "Status") - private Status status; - - public ExistingObjectReplication(@Nonnull @Element(name = "Status") Status status) { - this.status = Objects.requireNonNull(status, "Status must not be null"); - } - - public Status status() { - return this.status; - } -} diff --git a/api/src/main/java/io/minio/messages/Expiration.java b/api/src/main/java/io/minio/messages/Expiration.java deleted file mode 100644 index b2fc13541..000000000 --- a/api/src/main/java/io/minio/messages/Expiration.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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.messages; - -import java.time.ZonedDateTime; -import javax.annotation.Nullable; -import org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** Helper class to denote expiration information for {@link LifecycleRule}. */ -@Root(name = "Expiration") -public class Expiration extends DateDays { - @Element(name = "ExpiredObjectDeleteMarker", required = false) - private Boolean expiredObjectDeleteMarker; - - @Element(name = "ExpiredObjectAllVersions", required = false) - private Boolean expiredObjectAllVersions; // This is MinIO specific extension. - - public Expiration( - @Nullable @Element(name = "Date", required = false) ResponseDate date, - @Nullable @Element(name = "Days", required = false) Integer days, - @Nullable @Element(name = "ExpiredObjectDeleteMarker", required = false) - Boolean expiredObjectDeleteMarker) { - if (expiredObjectDeleteMarker != null) { - if (date != null || days != null) { - throw new IllegalArgumentException( - "ExpiredObjectDeleteMarker must not be provided along with Date and Days"); - } - } else if (date != null ^ days != null) { - this.date = date; - this.days = days; - } else { - throw new IllegalArgumentException("Only one of date or days must be set"); - } - - this.expiredObjectDeleteMarker = expiredObjectDeleteMarker; - } - - public Expiration(ZonedDateTime date, Integer days, Boolean expiredObjectDeleteMarker) { - this(date == null ? null : new ResponseDate(date), days, expiredObjectDeleteMarker); - } - - public Expiration( - @Nullable @Element(name = "Date", required = false) ResponseDate date, - @Nullable @Element(name = "Days", required = false) Integer days, - @Nullable @Element(name = "ExpiredObjectDeleteMarker", required = false) - Boolean expiredObjectDeleteMarker, - @Element(name = "ExpiredObjectAllVersions", required = false) - Boolean expiredObjectAllVersions) { - this(date, days, expiredObjectDeleteMarker); - this.expiredObjectAllVersions = expiredObjectAllVersions; - } - - public Boolean expiredObjectDeleteMarker() { - return expiredObjectDeleteMarker; - } - - /** This is MinIO specific extension. */ - public Boolean expiredObjectAllVersions() { - return expiredObjectAllVersions; - } -} diff --git a/api/src/main/java/io/minio/messages/FileHeaderInfo.java b/api/src/main/java/io/minio/messages/FileHeaderInfo.java deleted file mode 100644 index 46943f18f..000000000 --- a/api/src/main/java/io/minio/messages/FileHeaderInfo.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2019 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; - -/** Description the first line of input in CSV object. */ -public enum FileHeaderInfo { - USE, - IGNORE, - NONE; -} diff --git a/api/src/main/java/io/minio/messages/Filter.java b/api/src/main/java/io/minio/messages/Filter.java index dce52114c..46e1b0e01 100644 --- a/api/src/main/java/io/minio/messages/Filter.java +++ b/api/src/main/java/io/minio/messages/Filter.java @@ -1,5 +1,5 @@ /* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * 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. @@ -17,56 +17,231 @@ package io.minio.messages; import io.minio.Utils; -import java.util.LinkedList; -import java.util.List; -import org.simpleframework.xml.ElementList; +import java.util.Map; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.simpleframework.xml.Element; +import org.simpleframework.xml.ElementMap; import org.simpleframework.xml.Root; +import org.simpleframework.xml.convert.Convert; /** - * Helper class to denote Filter configuration of {@link CloudFunctionConfiguration}, {@link - * QueueConfiguration} or {@link TopicConfiguration}. + * Helper class to denote filter information for {@link ReplicationConfiguration.Rule} and {@link + * LifecycleConfiguration.Rule}. */ -@Root(name = "Filter", strict = false) +@Root(name = "Filter") public class Filter { - @ElementList(name = "S3Key") - private List filterRuleList; - - public Filter() {} - - /** - * Sets filter rule to list. As per Amazon AWS S3 server behavior, its not possible to set more - * than one rule for "prefix" or "suffix". However the spec - * http://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketPUTnotification.html is not clear - * about this behavior. - */ - private void setRule(String name, String value) throws IllegalArgumentException { - if (value.length() > 1024) { - throw new IllegalArgumentException("value '" + value + "' is more than 1024 long"); - } + @Element(name = "And", required = false) + private And andOperator; - if (filterRuleList == null) { - filterRuleList = new LinkedList<>(); - } + @Element(name = "Prefix", required = false) + @Convert(PrefixConverter.class) + private String prefix; - for (FilterRule rule : filterRuleList) { - // Remove rule.name is same as given name. - if (rule.name().equals(name)) { - filterRuleList.remove(rule); - } + @Element(name = "Tag", required = false) + private Tag tag; + + @Element(name = "ObjectSizeLessThan", required = false) + private Long objectSizeLessThan; + + @Element(name = "ObjectSizeGreaterThan", required = false) + private Long objectSizeGreaterThan; + + public Filter( + @Nullable @Element(name = "And", required = false) And andOperator, + @Nullable @Element(name = "Prefix", required = false) String prefix, + @Nullable @Element(name = "Tag", required = false) Tag tag) { + if (andOperator != null ^ prefix != null ^ tag != null) { + this.andOperator = andOperator; + this.prefix = prefix; + this.tag = tag; + } else { + throw new IllegalArgumentException("Only one of And, Prefix or Tag must be set"); } + } + + public Filter( + @Nullable @Element(name = "And", required = false) And andOperator, + @Nullable @Element(name = "Prefix", required = false) String prefix, + @Nullable @Element(name = "Tag", required = false) Tag tag, + @Nullable @Element(name = "ObjectSizeLessThan", required = false) Long objectSizeLessThan, + @Nullable @Element(name = "ObjectSizeGreaterThan", required = false) + Long objectSizeGreaterThan) { + this(andOperator, prefix, tag); + this.objectSizeLessThan = objectSizeLessThan; + this.objectSizeGreaterThan = objectSizeGreaterThan; + } + + public Filter(@Nonnull And andOperator) { + this.andOperator = Objects.requireNonNull(andOperator, "And operator must not be null"); + } + + public Filter(@Nonnull String prefix) { + this.prefix = Objects.requireNonNull(prefix, "Prefix must not be null"); + } + + public Filter(@Nonnull Tag tag) { + this.tag = Objects.requireNonNull(tag, "Tag must not be null"); + } + + public And andOperator() { + return this.andOperator; + } + + public String prefix() { + return this.prefix; + } + + public Tag tag() { + return this.tag; + } - filterRuleList.add(new FilterRule(name, value)); + public Long objectSizeLessThan() { + return this.objectSizeLessThan; } - public void setPrefixRule(String value) throws IllegalArgumentException { - setRule("prefix", value); + public Long objectSizeGreaterThan() { + return this.objectSizeGreaterThan; } - public void setSuffixRule(String value) throws IllegalArgumentException { - setRule("suffix", value); + @Override + public String toString() { + return String.format( + "Filter{andOperator=%s, prefix=%s, tag=%s, objectSizeLessThan=%s," + + " objectSizeGreaterThan=%s}", + Utils.stringify(andOperator), + Utils.stringify(prefix), + Utils.stringify(tag), + Utils.stringify(objectSizeLessThan), + Utils.stringify(objectSizeGreaterThan)); } - public List filterRuleList() { - return Utils.unmodifiableList(filterRuleList); + @Root(name = "And") + public static class And { + @Element(name = "Prefix", required = false) + @Convert(PrefixConverter.class) + private String prefix; + + @Element(name = "ObjectSizeLessThan", required = false) + private Long objectSizeLessThan; + + @Element(name = "ObjectSizeGreaterThan", required = false) + private Long objectSizeGreaterThan; + + @ElementMap( + attribute = false, + entry = "Tag", + inline = true, + key = "Key", + value = "Value", + required = false) + private Map tags; + + public And( + @Nullable @Element(name = "Prefix", required = false) String prefix, + @Nullable + @ElementMap( + attribute = false, + entry = "Tag", + inline = true, + key = "Key", + value = "Value", + required = false) + Map tags) { + if (prefix == null && tags == null) { + throw new IllegalArgumentException("At least Prefix or Tags must be set"); + } + + if (tags != null) { + for (String key : tags.keySet()) { + if (key.isEmpty()) { + throw new IllegalArgumentException("Tags must not contain empty key"); + } + } + } + + this.prefix = prefix; + this.tags = Utils.unmodifiableMap(tags); + } + + public And( + @Nullable @Element(name = "Prefix", required = false) String prefix, + @Nullable + @ElementMap( + attribute = false, + entry = "Tag", + inline = true, + key = "Key", + value = "Value", + required = false) + Map tags, + @Nullable @Element(name = "ObjectSizeLessThan", required = false) Long objectSizeLessThan, + @Nullable @Element(name = "ObjectSizeGreaterThan", required = false) + Long objectSizeGreaterThan) { + this(prefix, tags); + this.objectSizeLessThan = objectSizeLessThan; + this.objectSizeGreaterThan = objectSizeGreaterThan; + } + + public String prefix() { + return this.prefix; + } + + public Long objectSizeLessThan() { + return this.objectSizeLessThan; + } + + public Long objectSizeGreaterThan() { + return this.objectSizeGreaterThan; + } + + public Map tags() { + return this.tags; + } + + @Override + public String toString() { + return String.format( + "And{prefix=%s, objectSizeLessThan=%s, objectSizeGreaterThan=%s, tags=%s}", + Utils.stringify(prefix), + Utils.stringify(objectSizeLessThan), + Utils.stringify(objectSizeGreaterThan), + Utils.stringify(tags)); + } + } + + @Root(name = "Tag") + public static class Tag { + @Element(name = "Key") + private String key; + + @Element(name = "Value") + private String value; + + public Tag( + @Nonnull @Element(name = "Key") String key, + @Nonnull @Element(name = "Value") String value) { + Objects.requireNonNull(key, "Key must not be null"); + if (key.isEmpty()) { + throw new IllegalArgumentException("Key must not be empty"); + } + + this.key = key; + this.value = Objects.requireNonNull(value, "Value must not be null"); + } + + public String key() { + return this.key; + } + + public String value() { + return this.value; + } + + @Override + public String toString() { + return String.format("Tag{key=%s, value=%s}", Utils.stringify(key), Utils.stringify(value)); + } } } diff --git a/api/src/main/java/io/minio/messages/FilterRule.java b/api/src/main/java/io/minio/messages/FilterRule.java deleted file mode 100644 index 4eef68a27..000000000 --- a/api/src/main/java/io/minio/messages/FilterRule.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2017 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 org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** - * Helper class to denote FilterRule configuration of {@link CloudFunctionConfiguration}, {@link - * QueueConfiguration} or {@link TopicConfiguration}. - */ -@Root(name = "FilterRule", strict = false) -public class FilterRule { - @Element(name = "Name") - private String name; - - @Element(name = "Value") - private String value; - - public FilterRule() {} - - public FilterRule(String name, String value) { - this.name = name; - this.value = value; - } - - /** Returns filter name. */ - public String name() { - return name; - } - - /** Returns filter value. */ - public String value() { - return value; - } -} diff --git a/api/src/main/java/io/minio/messages/GetObjectAttributesOutput.java b/api/src/main/java/io/minio/messages/GetObjectAttributesOutput.java index f1b259763..a891fdc29 100644 --- a/api/src/main/java/io/minio/messages/GetObjectAttributesOutput.java +++ b/api/src/main/java/io/minio/messages/GetObjectAttributesOutput.java @@ -18,9 +18,7 @@ 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; @@ -97,50 +95,38 @@ 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; + @Override + public String toString() { + return String.format( + "GetObjectAttributesOutput{etag=%s, checksum=%s, objectParts=%s, storageClass=%s," + + " objectSize=%s, deleteMarker=%s, lastModified=%s, versionId=%s}", + Utils.stringify(etag), + Utils.stringify(checksum), + Utils.stringify(objectParts), + Utils.stringify(storageClass), + Utils.stringify(objectSize), + Utils.stringify(deleteMarker), + Utils.stringify(lastModified), + Utils.stringify(versionId)); + } + @Root(name = "ObjectParts", strict = false) + public static class ObjectParts extends BasePartsResult { @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 ObjectParts() { + super(); } public Integer partsCount() { return partsCount; } + + @Override + public String toString() { + return String.format( + "ObjectParts{partsCount=%s, %s}", Utils.stringify(partsCount), super.toString()); + } } } diff --git a/api/src/main/java/io/minio/messages/GlacierJobParameters.java b/api/src/main/java/io/minio/messages/GlacierJobParameters.java deleted file mode 100644 index 6e894e7b3..000000000 --- a/api/src/main/java/io/minio/messages/GlacierJobParameters.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2021 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 java.util.Objects; -import javax.annotation.Nonnull; -import org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** Helper class to denote S3 Glacier job parameters of {@link RestoreRequest}. */ -@Root(name = "GlacierJobParameters") -@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") -public class GlacierJobParameters { - @Element(name = "Tier") - private Tier tier; - - public GlacierJobParameters(@Nonnull Tier tier) { - this.tier = Objects.requireNonNull(tier, "Tier must not be null"); - } -} diff --git a/api/src/main/java/io/minio/messages/Grant.java b/api/src/main/java/io/minio/messages/Grant.java deleted file mode 100644 index 940240447..000000000 --- a/api/src/main/java/io/minio/messages/Grant.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2021 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 javax.annotation.Nullable; -import org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** Helper class to denote grant information of {@link AccessControlList}. */ -@Root(name = "Grant") -@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") -public class Grant { - @Element(name = "Grantee", required = false) - private Grantee grantee; - - @Element(name = "Permission", required = false) - private 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 deleted file mode 100644 index 42b17107d..000000000 --- a/api/src/main/java/io/minio/messages/Grantee.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2021 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 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; - - @Element(name = "EmailAddress", required = false) - private String emailAddress; - - @Element(name = "ID", required = false) - private String id; - - @Element(name = "Type") - private GranteeType type; - - @Element(name = "URI", required = false) - private String uri; - - public Grantee( - @Nonnull GranteeType type, - @Nullable String displayName, - @Nullable String emailAddress, - @Nullable String id, - @Nullable String uri) { - this.type = Objects.requireNonNull(type, "Type must not be null"); - this.displayName = displayName; - this.emailAddress = emailAddress; - 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/GranteeType.java b/api/src/main/java/io/minio/messages/GranteeType.java deleted file mode 100644 index 74311e2ea..000000000 --- a/api/src/main/java/io/minio/messages/GranteeType.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2021 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 org.simpleframework.xml.Root; -import org.simpleframework.xml.convert.Convert; -import org.simpleframework.xml.convert.Converter; -import org.simpleframework.xml.stream.InputNode; -import org.simpleframework.xml.stream.OutputNode; - -/** GranteeType represents type of grantee. */ -@Root(name = "Type") -@Convert(GranteeType.GranteeTypeConverter.class) -public enum GranteeType { - CANONICAL_USER("CanonicalUser"), - AMAZON_CUSTOMER_BY_EMAIL("AmazonCustomerByEmail"), - GROUP("Group"); - - private final String value; - - private GranteeType(String value) { - this.value = value; - } - - public String toString() { - return this.value; - } - - /** Returns GranteeType of given string. */ - public static GranteeType fromString(String granteeTypeString) { - for (GranteeType granteeType : GranteeType.values()) { - if (granteeTypeString.equals(granteeType.value)) { - return granteeType; - } - } - - throw new IllegalArgumentException("Unknown grantee type '" + granteeTypeString + "'"); - } - - /** XML converter class. */ - public static class GranteeTypeConverter implements Converter { - @Override - public GranteeType read(InputNode node) throws Exception { - return GranteeType.fromString(node.getValue()); - } - - @Override - public void write(OutputNode node, GranteeType granteeType) throws Exception { - node.setValue(granteeType.toString()); - } - } -} diff --git a/api/src/main/java/io/minio/messages/Identity.java b/api/src/main/java/io/minio/messages/Identity.java deleted file mode 100644 index 615f47343..000000000 --- a/api/src/main/java/io/minio/messages/Identity.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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.messages; - -import com.fasterxml.jackson.annotation.JsonProperty; - -/** Helper class to denote user or owner identity for {@link Event} and {@link BucketMetadata}. */ -@edu.umd.cs.findbugs.annotations.SuppressFBWarnings( - value = "UwF", - justification = "Everything in this class is initialized by JSON unmarshalling.") -public class Identity { - @JsonProperty private String principalId; - - public String principalId() { - return principalId; - } -} diff --git a/api/src/main/java/io/minio/messages/InitiateMultipartUploadResult.java b/api/src/main/java/io/minio/messages/InitiateMultipartUploadResult.java index b8a3c1519..ce17236be 100644 --- a/api/src/main/java/io/minio/messages/InitiateMultipartUploadResult.java +++ b/api/src/main/java/io/minio/messages/InitiateMultipartUploadResult.java @@ -16,6 +16,7 @@ package io.minio.messages; +import io.minio.Utils; import org.simpleframework.xml.Element; import org.simpleframework.xml.Namespace; import org.simpleframework.xml.Root; @@ -53,4 +54,11 @@ public String objectName() { public String uploadId() { return uploadId; } + + @Override + public String toString() { + return String.format( + "InitiateMultipartUploadResult{bucketName=%s, objectName=%s, uploadId=%s}", + Utils.stringify(bucketName), Utils.stringify(objectName), Utils.stringify(uploadId)); + } } diff --git a/api/src/main/java/io/minio/messages/Initiator.java b/api/src/main/java/io/minio/messages/Initiator.java index 84091c91b..c5ee75bbf 100644 --- a/api/src/main/java/io/minio/messages/Initiator.java +++ b/api/src/main/java/io/minio/messages/Initiator.java @@ -16,13 +16,10 @@ package io.minio.messages; +import io.minio.Utils; import org.simpleframework.xml.Element; import org.simpleframework.xml.Root; -/** - * Helper class to denote Initator information of a multipart upload and used in {@link - * ListMultipartUploadsResult} and {@link ListPartsResult}. - */ @Root(name = "Initiator", strict = false) public class Initiator { @Element(name = "ID", required = false) @@ -42,4 +39,10 @@ public String id() { public String displayName() { return displayName; } + + @Override + public String toString() { + return String.format( + "Initiator{id=%s, displayName=%s}", Utils.stringify(id), Utils.stringify(displayName)); + } } diff --git a/api/src/main/java/io/minio/messages/InputSerialization.java b/api/src/main/java/io/minio/messages/InputSerialization.java index a0e4e9003..2d638218b 100644 --- a/api/src/main/java/io/minio/messages/InputSerialization.java +++ b/api/src/main/java/io/minio/messages/InputSerialization.java @@ -27,16 +27,23 @@ public class InputSerialization { private CompressionType compressionType; @Element(name = "CSV", required = false) - private CsvInputSerialization csv; + private CSV csv; @Element(name = "JSON", required = false) - private JsonInputSerialization json; + private JSON json; @Element(name = "Parquet", required = false) - private ParquetInputSerialization parquet; + private Parquet parquet; + + private InputSerialization(CompressionType compressionType, CSV csv, JSON json, Parquet parquet) { + this.compressionType = compressionType; + this.csv = csv; + this.json = json; + this.parquet = parquet; + } /** Constructs a new InputSerialization object with CSV. */ - public InputSerialization( + public static InputSerialization newCSV( CompressionType compressionType, boolean allowQuotedRecordDelimiter, Character comments, @@ -45,26 +52,103 @@ public InputSerialization( Character quoteCharacter, Character quoteEscapeCharacter, Character recordDelimiter) { - this.compressionType = compressionType; - this.csv = - new CsvInputSerialization( + return new InputSerialization( + compressionType, + new CSV( allowQuotedRecordDelimiter, comments, fieldDelimiter, fileHeaderInfo, quoteCharacter, quoteEscapeCharacter, - recordDelimiter); + recordDelimiter), + null, + null); } /** Constructs a new InputSerialization object with JSON. */ - public InputSerialization(CompressionType compressionType, JsonType type) { - this.compressionType = compressionType; - this.json = new JsonInputSerialization(type); + public static InputSerialization newJSON(CompressionType compressionType, JsonType type) { + return new InputSerialization(compressionType, null, new JSON(type), null); } /** Constructs a new InputSerialization object with Parquet. */ - public InputSerialization() { - this.parquet = new ParquetInputSerialization(); + public static InputSerialization newParquet() { + return new InputSerialization(null, null, null, new Parquet()); + } + + @Root(name = "CSV") + @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") + public static class CSV { + @Element(name = "AllowQuotedRecordDelimiter", required = false) + private boolean allowQuotedRecordDelimiter; + + @Element(name = "Comments", required = false) + private Character comments; + + @Element(name = "FieldDelimiter", required = false) + private Character fieldDelimiter; + + @Element(name = "FileHeaderInfo", required = false) + private FileHeaderInfo fileHeaderInfo; + + @Element(name = "QuoteCharacter", required = false) + private Character quoteCharacter; + + @Element(name = "QuoteEscapeCharacter", required = false) + private Character quoteEscapeCharacter; + + @Element(name = "RecordDelimiter", required = false) + private Character recordDelimiter; + + public CSV( + boolean allowQuotedRecordDelimiter, + Character comments, + Character fieldDelimiter, + FileHeaderInfo fileHeaderInfo, + Character quoteCharacter, + Character quoteEscapeCharacter, + Character recordDelimiter) { + this.allowQuotedRecordDelimiter = allowQuotedRecordDelimiter; + this.comments = comments; + this.fieldDelimiter = fieldDelimiter; + this.fileHeaderInfo = fileHeaderInfo; + this.quoteCharacter = quoteCharacter; + this.quoteEscapeCharacter = quoteEscapeCharacter; + this.recordDelimiter = recordDelimiter; + } + } + + @Root(name = "JSON") + @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") + public static class JSON { + @Element(name = "Type", required = false) + private JsonType type; + + public JSON(JsonType type) { + this.type = type; + } + } + + @Root(name = "Parquet") + public static class Parquet {} + + /** CSV/JSON object's compression format for select object content. */ + public enum CompressionType { + NONE, + GZIP, + BZIP2; + } + + /** Description the first line of input in CSV object. */ + public enum FileHeaderInfo { + USE, + IGNORE, + NONE; + } + + /** The type of JSON. */ + public enum JsonType { + DOCUMENT, + LINES; } } diff --git a/api/src/main/java/io/minio/messages/Item.java b/api/src/main/java/io/minio/messages/Item.java index e13573cd6..f4022feeb 100644 --- a/api/src/main/java/io/minio/messages/Item.java +++ b/api/src/main/java/io/minio/messages/Item.java @@ -16,13 +16,21 @@ package io.minio.messages; +import io.minio.Time; import io.minio.Utils; import java.time.ZonedDateTime; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; +import javax.annotation.Nonnull; import org.simpleframework.xml.Element; import org.simpleframework.xml.ElementList; import org.simpleframework.xml.Root; +import org.simpleframework.xml.convert.Convert; +import org.simpleframework.xml.convert.Converter; +import org.simpleframework.xml.stream.InputNode; +import org.simpleframework.xml.stream.OutputNode; /** * Helper class to denote Object information in {@link ListBucketResultV1}, {@link @@ -36,7 +44,7 @@ public abstract class Item { private String objectName; @Element(name = "LastModified") - private ResponseDate lastModified; + private Time.S3Time lastModified; @Element(name = "Owner", required = false) private Owner owner; @@ -54,7 +62,7 @@ public abstract class Item { private String versionId; // except ListObjects V1 @Element(name = "UserMetadata", required = false) - private Metadata userMetadata; + private UserMetadata userMetadata; @Element(name = "UserTags", required = false) private String userTags; @@ -90,7 +98,7 @@ public String objectName() { /** Returns last modified time of the object. */ public ZonedDateTime lastModified() { - return (lastModified == null) ? null : lastModified.zonedDateTime(); + return lastModified == null ? null : lastModified.toZonedDateTime(); } /** Returns ETag of the object. */ @@ -139,7 +147,7 @@ public boolean isDir() { /** Returns whether this item is a delete marker or not. */ public boolean isDeleteMarker() { - return this instanceof DeleteMarker; + return this instanceof ListVersionsResult.DeleteMarker; } public List checksumAlgorithm() { @@ -154,17 +162,40 @@ public RestoreStatus restoreStatus() { return restoreStatus; } + @Override + public String toString() { + return String.format( + "etag=%s, objectName=%s, lastModified=%s, owner=%s, size=%s, storageClass=%s, isLatest=%s, " + + "versionId=%s, userMetadata=%s, userTags=%s, checksumAlgorithm=%s, checksumType=%s, " + + "restoreStatus=%s, isDir=%s, encodingType=%s", + Utils.stringify(etag), + Utils.stringify(objectName), + Utils.stringify(lastModified), + Utils.stringify(owner), + Utils.stringify(size), + Utils.stringify(storageClass), + Utils.stringify(isLatest), + Utils.stringify(versionId), + Utils.stringify(userMetadata), + Utils.stringify(userTags), + Utils.stringify(checksumAlgorithm), + Utils.stringify(checksumType), + Utils.stringify(restoreStatus), + Utils.stringify(isDir), + Utils.stringify(encodingType)); + } + @Root(name = "RestoreStatus", strict = false) public static class RestoreStatus { @Element(name = "IsRestoreInProgress", required = false) private Boolean isRestoreInProgress; @Element(name = "RestoreExpiryDate", required = false) - private ResponseDate restoreExpiryDate; + private Time.S3Time restoreExpiryDate; public RestoreStatus( @Element(name = "IsRestoreInProgress", required = false) Boolean isRestoreInProgress, - @Element(name = "RestoreExpiryDate", required = false) ResponseDate restoreExpiryDate) { + @Element(name = "RestoreExpiryDate", required = false) Time.S3Time restoreExpiryDate) { this.isRestoreInProgress = isRestoreInProgress; this.restoreExpiryDate = restoreExpiryDate; } @@ -174,7 +205,68 @@ public Boolean isRestoreInProgress() { } public ZonedDateTime restoreExpiryDate() { - return restoreExpiryDate == null ? null : restoreExpiryDate.zonedDateTime(); + return restoreExpiryDate == null ? null : restoreExpiryDate.toZonedDateTime(); + } + + @Override + public String toString() { + return String.format( + "RestoreStatus{isRestoreInProgress=%s, restoreExpiryDate=%s}", + Utils.stringify(isRestoreInProgress), Utils.stringify(restoreExpiryDate)); + } + } + + @Root(name = "UserMetadata") + @Convert(UserMetadata.UserMetadataConverter.class) + public static class UserMetadata { + Map map; + + public UserMetadata() {} + + public UserMetadata(@Nonnull Map map) { + this.map = + Utils.unmodifiableMap(Objects.requireNonNull(map, "User metadata must not be null")); + } + + public Map get() { + return map; + } + + @Override + public String toString() { + return String.format("UserMetadata{%s}", Utils.stringify(map)); + } + + /** XML converter class. */ + public static class UserMetadataConverter implements Converter { + @Override + public UserMetadata read(InputNode node) throws Exception { + Map map = new HashMap<>(); + while (true) { + InputNode childNode = node.getNext(); + if (childNode == null) { + break; + } + + map.put(childNode.getName(), childNode.getValue()); + } + + if (map.size() > 0) { + return new UserMetadata(map); + } + + return null; + } + + @Override + public void write(OutputNode node, UserMetadata metadata) throws Exception { + for (Map.Entry entry : metadata.get().entrySet()) { + OutputNode childNode = node.getChild(entry.getKey()); + childNode.setValue(entry.getValue()); + } + + node.commit(); + } } } } diff --git a/api/src/main/java/io/minio/messages/JsonInputSerialization.java b/api/src/main/java/io/minio/messages/JsonInputSerialization.java deleted file mode 100644 index 21e7b52dd..000000000 --- a/api/src/main/java/io/minio/messages/JsonInputSerialization.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2019 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 org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** - * Helper class to denote JSON input serialization request XML as per {@link - * SelectObjectContentRequest}. - */ -@Root(name = "JSON") -@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") -public class JsonInputSerialization { - @Element(name = "Type", required = false) - private JsonType type; - - /** Constructs a new JsonInputSerialization object. */ - public JsonInputSerialization(JsonType type) { - this.type = type; - } -} diff --git a/api/src/main/java/io/minio/messages/JsonOutputSerialization.java b/api/src/main/java/io/minio/messages/JsonOutputSerialization.java deleted file mode 100644 index 5fbc2b294..000000000 --- a/api/src/main/java/io/minio/messages/JsonOutputSerialization.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2019 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 org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** - * Helper class to denote JSON output serialization request XML as per {@link - * SelectObjectContentRequest}. - */ -@Root(name = "JSON") -@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") -public class JsonOutputSerialization { - @Element(name = "RecordDelimiter", required = false) - private Character recordDelimiter; - - /** Constructs a new JsonOutputSerialization object. */ - public JsonOutputSerialization(Character recordDelimiter) { - this.recordDelimiter = recordDelimiter; - } -} diff --git a/api/src/main/java/io/minio/messages/JsonType.java b/api/src/main/java/io/minio/messages/JsonType.java deleted file mode 100644 index d1e79865e..000000000 --- a/api/src/main/java/io/minio/messages/JsonType.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2019 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; - -/** The type of JSON. */ -public enum JsonType { - DOCUMENT, - LINES; -} diff --git a/api/src/main/java/io/minio/messages/LegalHold.java b/api/src/main/java/io/minio/messages/LegalHold.java index 913c8e213..fb463cc27 100644 --- a/api/src/main/java/io/minio/messages/LegalHold.java +++ b/api/src/main/java/io/minio/messages/LegalHold.java @@ -16,6 +16,7 @@ package io.minio.messages; +import io.minio.Utils; import org.simpleframework.xml.Element; import org.simpleframework.xml.Namespace; import org.simpleframework.xml.Root; @@ -48,4 +49,9 @@ public LegalHold(boolean status) { public boolean status() { return status != null && status.equals("ON"); } + + @Override + public String toString() { + return String.format("LegalHold{status=%s}", Utils.stringify(status)); + } } diff --git a/api/src/main/java/io/minio/messages/LifecycleConfiguration.java b/api/src/main/java/io/minio/messages/LifecycleConfiguration.java index 72659cb0f..f7874f412 100644 --- a/api/src/main/java/io/minio/messages/LifecycleConfiguration.java +++ b/api/src/main/java/io/minio/messages/LifecycleConfiguration.java @@ -16,10 +16,14 @@ package io.minio.messages; +import io.minio.Time; import io.minio.Utils; +import java.time.ZonedDateTime; import java.util.List; import java.util.Objects; import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.simpleframework.xml.Element; import org.simpleframework.xml.ElementList; import org.simpleframework.xml.Namespace; import org.simpleframework.xml.Root; @@ -35,18 +39,356 @@ @Namespace(reference = "http://s3.amazonaws.com/doc/2006-03-01/") public class LifecycleConfiguration { @ElementList(name = "Rule", inline = true) - private List rules; + private List rules; /** Constructs new lifecycle configuration. */ public LifecycleConfiguration( - @Nonnull @ElementList(name = "Rule", inline = true) List rules) { - this.rules = Utils.unmodifiableList(Objects.requireNonNull(rules, "Rules must not be null")); - if (rules.isEmpty()) { + @Nonnull @ElementList(name = "Rule", inline = true) List rules) { + if (Objects.requireNonNull(rules, "Rules must not be null").isEmpty()) { throw new IllegalArgumentException("Rules must not be empty"); } + this.rules = Utils.unmodifiableList(rules); } - public List rules() { + public List rules() { return rules; } + + @Override + public String toString() { + return String.format("LifecycleConfiguration{rules=%s}", Utils.stringify(rules)); + } + + @Root(name = "Rule") + public static class Rule { + @Element(name = "AbortIncompleteMultipartUpload", required = false) + private AbortIncompleteMultipartUpload abortIncompleteMultipartUpload; + + @Element(name = "Expiration", required = false) + private Expiration expiration; + + @Element(name = "Filter", required = false) + private Filter filter; + + @Element(name = "ID", required = false) + private String id; + + @Element(name = "NoncurrentVersionExpiration", required = false) + private NoncurrentVersionExpiration noncurrentVersionExpiration; + + @Element(name = "NoncurrentVersionTransition", required = false) + private NoncurrentVersionTransition noncurrentVersionTransition; + + @Element(name = "Status") + private Status status; + + @Element(name = "Transition", required = false) + private Transition transition; + + /** Constructs new server-side encryption configuration rule. */ + public Rule( + @Nonnull @Element(name = "Status") Status status, + @Nullable @Element(name = "AbortIncompleteMultipartUpload", required = false) + AbortIncompleteMultipartUpload abortIncompleteMultipartUpload, + @Nullable @Element(name = "Expiration", required = false) Expiration expiration, + @Nonnull @Element(name = "Filter", required = false) Filter filter, + @Nullable @Element(name = "ID", required = false) String id, + @Nullable @Element(name = "NoncurrentVersionExpiration", required = false) + NoncurrentVersionExpiration noncurrentVersionExpiration, + @Nullable @Element(name = "NoncurrentVersionTransition", required = false) + NoncurrentVersionTransition noncurrentVersionTransition, + @Nullable @Element(name = "Transition", required = false) Transition transition) { + if (abortIncompleteMultipartUpload == null + && expiration == null + && noncurrentVersionExpiration == null + && noncurrentVersionTransition == null + && transition == null) { + throw new IllegalArgumentException( + "At least one of action (AbortIncompleteMultipartUpload, Expiration, " + + "NoncurrentVersionExpiration, NoncurrentVersionTransition or Transition) must be " + + "specified in a rule"); + } + + if (id != null) { + id = id.trim(); + if (id.isEmpty()) throw new IllegalArgumentException("ID must be non-empty string"); + if (id.length() > 255) + throw new IllegalArgumentException("ID must not exceed 255 characters"); + } + + this.abortIncompleteMultipartUpload = abortIncompleteMultipartUpload; + this.expiration = expiration; + this.filter = Objects.requireNonNull(filter, "Filter must not be null"); + this.id = id; + this.noncurrentVersionExpiration = noncurrentVersionExpiration; + this.noncurrentVersionTransition = noncurrentVersionTransition; + this.status = Objects.requireNonNull(status, "Status must not be null"); + this.transition = transition; + } + + public AbortIncompleteMultipartUpload abortIncompleteMultipartUpload() { + return abortIncompleteMultipartUpload; + } + + public Expiration expiration() { + return expiration; + } + + public Filter filter() { + return this.filter; + } + + public String id() { + return this.id; + } + + public NoncurrentVersionExpiration noncurrentVersionExpiration() { + return noncurrentVersionExpiration; + } + + public NoncurrentVersionTransition noncurrentVersionTransition() { + return noncurrentVersionTransition; + } + + public Status status() { + return this.status; + } + + public Transition transition() { + return transition; + } + + @Override + public String toString() { + return String.format( + "Rule{abortIncompleteMultipartUpload=%s, expiration=%s, filter=%s, id=%s," + + " noncurrentVersionExpiration=%s, noncurrentVersionTransition=%s, status=%s," + + " transition=%s}", + Utils.stringify(abortIncompleteMultipartUpload), + Utils.stringify(expiration), + Utils.stringify(filter), + Utils.stringify(id), + Utils.stringify(noncurrentVersionExpiration), + Utils.stringify(noncurrentVersionTransition), + Utils.stringify(status), + Utils.stringify(transition)); + } + } + + @Root(name = "AbortIncompleteMultipartUpload") + public static class AbortIncompleteMultipartUpload { + @Element(name = "DaysAfterInitiation") + private int daysAfterInitiation; + + public AbortIncompleteMultipartUpload( + @Element(name = "DaysAfterInitiation") int daysAfterInitiation) { + this.daysAfterInitiation = daysAfterInitiation; + } + + public int daysAfterInitiation() { + return daysAfterInitiation; + } + + @Override + public String toString() { + return String.format( + "AbortIncompleteMultipartUpload{daysAfterInitiation=%d}", daysAfterInitiation); + } + } + + public abstract static class DateDays { + @Element(name = "Date", required = false) + protected Time.S3Time date; + + @Element(name = "Days", required = false) + protected Integer days; + + public ZonedDateTime date() { + return date == null ? null : date.toZonedDateTime(); + } + + public Integer days() { + return days; + } + + @Override + public String toString() { + return String.format("date=%s, days=%s", Utils.stringify(date), Utils.stringify(days)); + } + } + + @Root(name = "Expiration") + public static class Expiration extends DateDays { + @Element(name = "ExpiredObjectDeleteMarker", required = false) + private Boolean expiredObjectDeleteMarker; + + @Element(name = "ExpiredObjectAllVersions", required = false) + private Boolean expiredObjectAllVersions; // This is MinIO specific extension. + + public Expiration( + @Nullable @Element(name = "Date", required = false) Time.S3Time date, + @Nullable @Element(name = "Days", required = false) Integer days, + @Nullable @Element(name = "ExpiredObjectDeleteMarker", required = false) + Boolean expiredObjectDeleteMarker) { + if (expiredObjectDeleteMarker != null) { + if (date != null || days != null) { + throw new IllegalArgumentException( + "ExpiredObjectDeleteMarker must not be provided along with Date and Days"); + } + } else if (date != null ^ days != null) { + this.date = date; + this.days = days; + } else { + throw new IllegalArgumentException("Only one of date or days must be set"); + } + + this.expiredObjectDeleteMarker = expiredObjectDeleteMarker; + } + + public Expiration(ZonedDateTime date, Integer days, Boolean expiredObjectDeleteMarker) { + this(date == null ? null : new Time.S3Time(date), days, expiredObjectDeleteMarker); + } + + public Expiration( + @Nullable @Element(name = "Date", required = false) Time.S3Time date, + @Nullable @Element(name = "Days", required = false) Integer days, + @Nullable @Element(name = "ExpiredObjectDeleteMarker", required = false) + Boolean expiredObjectDeleteMarker, + @Element(name = "ExpiredObjectAllVersions", required = false) + Boolean expiredObjectAllVersions) { + this(date, days, expiredObjectDeleteMarker); + this.expiredObjectAllVersions = expiredObjectAllVersions; + } + + public Boolean expiredObjectDeleteMarker() { + return expiredObjectDeleteMarker; + } + + public Boolean expiredObjectAllVersions() { + return expiredObjectAllVersions; + } + + @Override + public String toString() { + return String.format( + "Expiration{%s, expiredObjectDeleteMarker=%s, expiredObjectAllVersions=%s}", + super.toString(), + Utils.stringify(expiredObjectDeleteMarker), + Utils.stringify(expiredObjectAllVersions)); + } + } + + @Root(name = "NoncurrentVersionExpiration") + public static class NoncurrentVersionExpiration { + @Element(name = "NoncurrentDays") + private int noncurrentDays; + + @Element(name = "NewerNoncurrentVersions", required = false) + private Integer newerNoncurrentVersions; + + public NoncurrentVersionExpiration( + @Element(name = "NoncurrentDays", required = false) int noncurrentDays) { + this.noncurrentDays = noncurrentDays; + } + + public NoncurrentVersionExpiration( + @Element(name = "NoncurrentDays", required = false) int noncurrentDays, + @Element(name = "NewerNoncurrentVersions", required = false) + Integer newerNoncurrentVersions) { + this.noncurrentDays = noncurrentDays; + this.newerNoncurrentVersions = newerNoncurrentVersions; + } + + public int noncurrentDays() { + return noncurrentDays; + } + + public Integer newerNoncurrentVersions() { + return newerNoncurrentVersions; + } + + protected String stringify() { + return String.format( + "noncurrentDays=%d, newerNoncurrentVersions=%s", + noncurrentDays, Utils.stringify(newerNoncurrentVersions)); + } + + @Override + public String toString() { + return String.format("NoncurrentVersionExpiration{%s}", stringify()); + } + } + + @Root(name = "NoncurrentVersionTransition") + public static class NoncurrentVersionTransition extends NoncurrentVersionExpiration { + @Element(name = "StorageClass") + private String storageClass; + + public NoncurrentVersionTransition( + @Element(name = "NoncurrentDays", required = false) int noncurrentDays, + @Nonnull @Element(name = "StorageClass", required = false) String storageClass) { + super(noncurrentDays, null); + if (storageClass == null || storageClass.isEmpty()) { + throw new IllegalArgumentException("StorageClass must be provided"); + } + this.storageClass = storageClass; + } + + public NoncurrentVersionTransition( + @Element(name = "NoncurrentDays", required = false) int noncurrentDays, + @Element(name = "NewerNoncurrentVersions", required = false) + Integer newerNoncurrentVersions, + @Nonnull @Element(name = "StorageClass", required = false) String storageClass) { + super(noncurrentDays, newerNoncurrentVersions); + if (storageClass == null || storageClass.isEmpty()) { + throw new IllegalArgumentException("StorageClass must be provided"); + } + this.storageClass = storageClass; + } + + public String storageClass() { + return storageClass; + } + + @Override + public String toString() { + return String.format( + "NoncurrentVersionTransition{%s, storageClass=%s}", super.stringify(), storageClass); + } + } + + @Root(name = "Transition") + public static class Transition extends DateDays { + @Element(name = "StorageClass") + private String storageClass; + + public Transition( + @Nullable @Element(name = "Date", required = false) Time.S3Time date, + @Nullable @Element(name = "Days", required = false) Integer days, + @Nonnull @Element(name = "StorageClass", required = false) String storageClass) { + if (date != null ^ days != null) { + this.date = date; + this.days = days; + } else { + throw new IllegalArgumentException("Only one of date or days must be set"); + } + if (storageClass == null || storageClass.isEmpty()) { + throw new IllegalArgumentException("StorageClass must be provided"); + } + this.storageClass = storageClass; + } + + public Transition(ZonedDateTime date, Integer days, String storageClass) { + this(date == null ? null : new Time.S3Time(date), days, storageClass); + } + + public String storageClass() { + return storageClass; + } + + @Override + public String toString() { + return String.format("Transition{%s, storageClass=%s}", super.toString(), storageClass); + } + } } diff --git a/api/src/main/java/io/minio/messages/LifecycleRule.java b/api/src/main/java/io/minio/messages/LifecycleRule.java deleted file mode 100644 index ab338ca3a..000000000 --- a/api/src/main/java/io/minio/messages/LifecycleRule.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * 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.messages; - -import java.util.Objects; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** Helper class to denote Rule information for {@link LifecycleConfiguration}. */ -@Root(name = "Rule") -public class LifecycleRule { - @Element(name = "AbortIncompleteMultipartUpload", required = false) - private AbortIncompleteMultipartUpload abortIncompleteMultipartUpload; - - @Element(name = "Expiration", required = false) - private Expiration expiration; - - @Element(name = "Filter", required = false) - private RuleFilter filter; - - @Element(name = "ID", required = false) - private String id; - - @Element(name = "NoncurrentVersionExpiration", required = false) - private NoncurrentVersionExpiration noncurrentVersionExpiration; - - @Element(name = "NoncurrentVersionTransition", required = false) - private NoncurrentVersionTransition noncurrentVersionTransition; - - @Element(name = "Status") - private Status status; - - @Element(name = "Transition", required = false) - private Transition transition; - - /** Constructs new server-side encryption configuration rule. */ - public LifecycleRule( - @Nonnull @Element(name = "Status") Status status, - @Nullable @Element(name = "AbortIncompleteMultipartUpload", required = false) - AbortIncompleteMultipartUpload abortIncompleteMultipartUpload, - @Nullable @Element(name = "Expiration", required = false) Expiration expiration, - @Nonnull @Element(name = "Filter", required = false) RuleFilter filter, - @Nullable @Element(name = "ID", required = false) String id, - @Nullable @Element(name = "NoncurrentVersionExpiration", required = false) - NoncurrentVersionExpiration noncurrentVersionExpiration, - @Nullable @Element(name = "NoncurrentVersionTransition", required = false) - NoncurrentVersionTransition noncurrentVersionTransition, - @Nullable @Element(name = "Transition", required = false) Transition transition) { - if (abortIncompleteMultipartUpload == null - && expiration == null - && noncurrentVersionExpiration == null - && noncurrentVersionTransition == null - && transition == null) { - throw new IllegalArgumentException( - "At least one of action (AbortIncompleteMultipartUpload, Expiration, " - + "NoncurrentVersionExpiration, NoncurrentVersionTransition or Transition) must be " - + "specified in a rule"); - } - - if (id != null) { - id = id.trim(); - if (id.isEmpty()) throw new IllegalArgumentException("ID must be non-empty string"); - if (id.length() > 255) throw new IllegalArgumentException("ID must be exceed 255 characters"); - } - - this.abortIncompleteMultipartUpload = abortIncompleteMultipartUpload; - this.expiration = expiration; - this.filter = Objects.requireNonNull(filter, "Filter must not be null"); - this.id = id; - this.noncurrentVersionExpiration = noncurrentVersionExpiration; - this.noncurrentVersionTransition = noncurrentVersionTransition; - this.status = Objects.requireNonNull(status, "Status must not be null"); - this.transition = transition; - } - - public AbortIncompleteMultipartUpload abortIncompleteMultipartUpload() { - return abortIncompleteMultipartUpload; - } - - public Expiration expiration() { - return expiration; - } - - public RuleFilter filter() { - return this.filter; - } - - public String id() { - return this.id; - } - - public NoncurrentVersionExpiration noncurrentVersionExpiration() { - return noncurrentVersionExpiration; - } - - public NoncurrentVersionTransition noncurrentVersionTransition() { - return noncurrentVersionTransition; - } - - public Status status() { - return this.status; - } - - public Transition transition() { - return transition; - } -} diff --git a/api/src/main/java/io/minio/messages/ListAllMyBucketsResult.java b/api/src/main/java/io/minio/messages/ListAllMyBucketsResult.java index f6909996f..0ef1c3b15 100644 --- a/api/src/main/java/io/minio/messages/ListAllMyBucketsResult.java +++ b/api/src/main/java/io/minio/messages/ListAllMyBucketsResult.java @@ -16,7 +16,9 @@ package io.minio.messages; +import io.minio.Time; import io.minio.Utils; +import java.time.ZonedDateTime; import java.util.List; import org.simpleframework.xml.Element; import org.simpleframework.xml.ElementList; @@ -61,4 +63,49 @@ public String prefix() { public String continuationToken() { return continuationToken; } + + @Override + public String toString() { + return String.format( + "ListAllMyBucketsResult{owner=%s, buckets=%s, prefix=%s, continuationToken=%s}", + Utils.stringify(owner), + Utils.stringify(buckets), + Utils.stringify(prefix), + Utils.stringify(continuationToken)); + } + + @Root(name = "Bucket", strict = false) + public static class Bucket { + @Element(name = "Name") + private String name; + + @Element(name = "CreationDate") + private Time.S3Time creationDate; + + @Element(name = "BucketRegion", required = false) + private String bucketRegion; + + public Bucket() {} + + /** Returns bucket name. */ + public String name() { + return name; + } + + /** Returns creation date. */ + public ZonedDateTime creationDate() { + return creationDate == null ? null : creationDate.toZonedDateTime(); + } + + public String bucketRegion() { + return bucketRegion; + } + + @Override + public String toString() { + return String.format( + "Bucket{name=%s, creationDate=%s, bucketRegion=%s}", + Utils.stringify(name), Utils.stringify(creationDate), Utils.stringify(bucketRegion)); + } + } } diff --git a/api/src/main/java/io/minio/messages/ListBucketResultV1.java b/api/src/main/java/io/minio/messages/ListBucketResultV1.java index a88bde5d8..6a83cf206 100644 --- a/api/src/main/java/io/minio/messages/ListBucketResultV1.java +++ b/api/src/main/java/io/minio/messages/ListBucketResultV1.java @@ -51,4 +51,14 @@ public String nextMarker() { public List contents() { return Utils.unmodifiableList(contents); } + + @Override + public String toString() { + return String.format( + "ListBucketResultV1{%s, marker=%s, nextMarker=%s, contents=%s}", + super.toString(), + Utils.stringify(marker), + Utils.stringify(nextMarker), + Utils.stringify(contents)); + } } diff --git a/api/src/main/java/io/minio/messages/ListBucketResultV2.java b/api/src/main/java/io/minio/messages/ListBucketResultV2.java index 5d67d1ffd..c422bf742 100644 --- a/api/src/main/java/io/minio/messages/ListBucketResultV2.java +++ b/api/src/main/java/io/minio/messages/ListBucketResultV2.java @@ -71,4 +71,17 @@ public String nextContinuationToken() { public List contents() { return Utils.unmodifiableList(contents); } + + @Override + public String toString() { + return String.format( + "ListBucketResultV2{%s, keyCount=%s, startAfter=%s, continuationToken=%s," + + " nextContinuationToken=%s, contents=%s}", + super.toString(), + Utils.stringify(keyCount), + Utils.stringify(startAfter), + Utils.stringify(continuationToken), + Utils.stringify(nextContinuationToken), + Utils.stringify(contents)); + } } diff --git a/api/src/main/java/io/minio/messages/ListMultipartUploadsResult.java b/api/src/main/java/io/minio/messages/ListMultipartUploadsResult.java index 3cecf55f7..b45b9fa9b 100644 --- a/api/src/main/java/io/minio/messages/ListMultipartUploadsResult.java +++ b/api/src/main/java/io/minio/messages/ListMultipartUploadsResult.java @@ -16,7 +16,9 @@ package io.minio.messages; +import io.minio.Time; import io.minio.Utils; +import java.time.ZonedDateTime; import java.util.List; import org.simpleframework.xml.Element; import org.simpleframework.xml.ElementList; @@ -103,4 +105,122 @@ public String encodingType() { public List uploads() { return Utils.unmodifiableList(uploads); } + + @Override + public String toString() { + return String.format( + "ListMultipartUploadsResult{bucketName=%s, encodingType=%s, keyMarker=%s," + + " uploadIdMarker=%s, nextKeyMarker=%s, nextUploadIdMarker=%s, maxUploads=%s," + + " isTruncated=%s, uploads=%s}", + Utils.stringify(bucketName), + Utils.stringify(encodingType), + Utils.stringify(keyMarker), + Utils.stringify(uploadIdMarker), + Utils.stringify(nextKeyMarker), + Utils.stringify(nextUploadIdMarker), + Utils.stringify(maxUploads), + Utils.stringify(isTruncated), + Utils.stringify(uploads)); + } + + @Root(name = "Upload", strict = false) + @Namespace(reference = "http://s3.amazonaws.com/doc/2006-03-01/") + public static class Upload { + @Element(name = "Key") + private String objectName; + + @Element(name = "UploadId") + private String uploadId; + + @Element(name = "Initiator") + private Initiator initiator; + + @Element(name = "Owner") + private Owner owner; + + @Element(name = "StorageClass") + private String storageClass; + + @Element(name = "Initiated") + private Time.S3Time initiated; + + @Element(name = "ChecksumAlgorithm", required = false) + private String checksumAlgorithm; + + @Element(name = "ChecksumType", required = false) + private String checksumType; + + private long aggregatedPartSize; + private String encodingType = null; + + public Upload() {} + + /** Returns object name. */ + public String objectName() { + return Utils.urlDecode(objectName, encodingType); + } + + /** Returns upload ID. */ + public String uploadId() { + return uploadId; + } + + /** Returns initiator information. */ + public Initiator initiator() { + return initiator; + } + + /** Returns owner information. */ + public Owner owner() { + return owner; + } + + /** Returns storage class. */ + public String storageClass() { + return storageClass; + } + + /** Returns initiated time. */ + public ZonedDateTime initiated() { + return initiated == null ? null : initiated.toZonedDateTime(); + } + + /** Returns aggregated part size. */ + public long aggregatedPartSize() { + return aggregatedPartSize; + } + + /** Sets given aggregated part size. */ + public void setAggregatedPartSize(long size) { + this.aggregatedPartSize = size; + } + + public void setEncodingType(String encodingType) { + this.encodingType = encodingType; + } + + public String checksumAlgorithm() { + return checksumAlgorithm; + } + + public String checksumType() { + return checksumType; + } + + @Override + public String toString() { + return String.format( + "Upload{objectName=%s, uploadId=%s, initiator=%s, owner=%s, storageClass=%s, " + + "initiated=%s, checksumAlgorithm=%s, checksumType=%s, aggregatedPartSize=%s}", + Utils.stringify(objectName), + Utils.stringify(uploadId), + Utils.stringify(initiator), + Utils.stringify(owner), + Utils.stringify(storageClass), + Utils.stringify(initiated), + Utils.stringify(checksumAlgorithm), + Utils.stringify(checksumType), + Utils.stringify(aggregatedPartSize)); + } + } } diff --git a/api/src/main/java/io/minio/messages/ListObjectsResult.java b/api/src/main/java/io/minio/messages/ListObjectsResult.java index 235001676..a57074998 100644 --- a/api/src/main/java/io/minio/messages/ListObjectsResult.java +++ b/api/src/main/java/io/minio/messages/ListObjectsResult.java @@ -20,6 +20,7 @@ import java.util.List; import org.simpleframework.xml.Element; import org.simpleframework.xml.ElementList; +import org.simpleframework.xml.Root; /** * Base class of {@link ListBucketResultV1}, {@link ListBucketResultV2} and {@link @@ -47,7 +48,8 @@ public abstract class ListObjectsResult { @ElementList(name = "CommonPrefixes", inline = true, required = false) private List commonPrefixes; - private static final List deleteMarkers = Utils.unmodifiableList(null); + private static final List deleteMarkers = + Utils.unmodifiableList(null); public ListObjectsResult() {} @@ -85,9 +87,40 @@ public List commonPrefixes() { return Utils.unmodifiableList(commonPrefixes); } - public List deleteMarkers() { + public List deleteMarkers() { return deleteMarkers; } public abstract List contents(); + + @Override + public String toString() { + return String.format( + "name=%s, encodingType=%s, prefix=%s, delimiter=%s, isTruncated=%s, maxKeys=%s, commonPrefixes=%s, deleteMarkers=%s", + Utils.stringify(name), + Utils.stringify(encodingType), + Utils.stringify(prefix), + Utils.stringify(delimiter), + Utils.stringify(isTruncated), + Utils.stringify(maxKeys), + Utils.stringify(commonPrefixes), + Utils.stringify(deleteMarkers)); + } + + @Root(name = "CommonPrefixes", strict = false) + public static class Prefix { + @Element(name = "Prefix") + private String prefix; + + public Prefix() {} + + public Item toItem() { + return new Contents(prefix); + } + + @Override + public String toString() { + return String.format("Prefix{%s}", Utils.stringify(prefix)); + } + } } diff --git a/api/src/main/java/io/minio/messages/ListPartsResult.java b/api/src/main/java/io/minio/messages/ListPartsResult.java index 9bdd33489..c186d7f98 100644 --- a/api/src/main/java/io/minio/messages/ListPartsResult.java +++ b/api/src/main/java/io/minio/messages/ListPartsResult.java @@ -17,9 +17,7 @@ package io.minio.messages; import io.minio.Utils; -import java.util.List; import org.simpleframework.xml.Element; -import org.simpleframework.xml.ElementList; import org.simpleframework.xml.Namespace; import org.simpleframework.xml.Root; @@ -29,7 +27,7 @@ */ @Root(name = "ListPartsResult", strict = false) @Namespace(reference = "http://s3.amazonaws.com/doc/2006-03-01/") -public class ListPartsResult { +public class ListPartsResult extends BasePartsResult { @Element(name = "Bucket") private String bucketName; @@ -45,21 +43,6 @@ public class ListPartsResult { @Element(name = "StorageClass") private String storageClass; - @Element(name = "PartNumberMarker") - private int partNumberMarker; - - @Element(name = "NextPartNumberMarker") - private int nextPartNumberMarker; - - @Element(name = "MaxParts") - private int maxParts; - - @Element(name = "IsTruncated") - private boolean isTruncated; - - @ElementList(name = "Part", inline = true, required = false) - private List partList; - @Element(name = "UploadId", required = false) private String uploadId; @@ -69,7 +52,9 @@ public class ListPartsResult { @Element(name = "ChecksumType", required = false) private String checksumType; - public ListPartsResult() {} + public ListPartsResult() { + super(); + } /** Returns bucket name. */ public String bucketName() { @@ -96,31 +81,6 @@ public Owner owner() { return owner; } - /** Returns maximum parts information received. */ - public int maxParts() { - return maxParts; - } - - /** Returns whether the result is truncated or not. */ - public boolean isTruncated() { - return isTruncated; - } - - /** Returns part number marker. */ - public int partNumberMarker() { - return partNumberMarker; - } - - /** Returns next part number marker. */ - public int nextPartNumberMarker() { - return nextPartNumberMarker; - } - - /** Returns List of Part. */ - public List partList() { - return Utils.unmodifiableList(partList); - } - public String uploadId() { return uploadId; } @@ -132,4 +92,20 @@ public String checksumAlgorithm() { public String checksumType() { return checksumType; } + + @Override + public String toString() { + return String.format( + "ListPartsResult{bucketName=%s, objectName=%s, initiator=%s, owner=%s, storageClass=%s," + + " uploadId=%s, checksumAlgorithm=%s, checksumType=%s, %s}", + Utils.stringify(bucketName), + Utils.stringify(objectName), + Utils.stringify(initiator), + Utils.stringify(owner), + Utils.stringify(storageClass), + Utils.stringify(uploadId), + Utils.stringify(checksumAlgorithm), + Utils.stringify(checksumType), + super.toString()); + } } diff --git a/api/src/main/java/io/minio/messages/ListVersionsResult.java b/api/src/main/java/io/minio/messages/ListVersionsResult.java index 13e22f522..6a1244411 100644 --- a/api/src/main/java/io/minio/messages/ListVersionsResult.java +++ b/api/src/main/java/io/minio/messages/ListVersionsResult.java @@ -74,4 +74,50 @@ public List contents() { public List deleteMarkers() { return Utils.unmodifiableList(deleteMarkers); } + + @Override + public String toString() { + return String.format( + "ListVersionsResult{%s, keyMarker=%s, nextKeyMarker=%s, versionIdMarker=%s," + + " nextVersionIdMarker=%s, contents=%s, deleteMarkers=%s}", + super.toString(), + Utils.stringify(keyMarker), + Utils.stringify(nextKeyMarker), + Utils.stringify(versionIdMarker), + Utils.stringify(nextVersionIdMarker), + Utils.stringify(contents), + Utils.stringify(deleteMarkers)); + } + + @Root(name = "Version", strict = false) + public static class Version extends Item { + public Version() { + super(); + } + + public Version(String prefix) { + super(prefix); + } + + @Override + public String toString() { + return String.format("Version{%s}", super.toString()); + } + } + + @Root(name = "DeleteMarker", strict = false) + public static class DeleteMarker extends Item { + public DeleteMarker() { + super(); + } + + public DeleteMarker(String prefix) { + super(prefix); + } + + @Override + public String toString() { + return String.format("DeleteMarker{%s}", super.toString()); + } + } } diff --git a/api/src/main/java/io/minio/messages/LocationConstraint.java b/api/src/main/java/io/minio/messages/LocationConstraint.java index d7c249cea..09ea5cb46 100644 --- a/api/src/main/java/io/minio/messages/LocationConstraint.java +++ b/api/src/main/java/io/minio/messages/LocationConstraint.java @@ -16,6 +16,7 @@ package io.minio.messages; +import io.minio.Utils; import org.simpleframework.xml.Namespace; import org.simpleframework.xml.Root; import org.simpleframework.xml.Text; @@ -36,4 +37,9 @@ public LocationConstraint() {} public String location() { return location; } + + @Override + public String toString() { + return String.format("LocationConstraint{location=%s}", Utils.stringify(location)); + } } diff --git a/api/src/main/java/io/minio/messages/Metadata.java b/api/src/main/java/io/minio/messages/Metadata.java deleted file mode 100644 index 5dab1aaff..000000000 --- a/api/src/main/java/io/minio/messages/Metadata.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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.messages; - -import io.minio.Utils; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import javax.annotation.Nonnull; -import org.simpleframework.xml.Root; -import org.simpleframework.xml.convert.Convert; -import org.simpleframework.xml.convert.Converter; -import org.simpleframework.xml.stream.InputNode; -import org.simpleframework.xml.stream.OutputNode; - -/** XML friendly map denotes metadata. */ -@Root(name = "Metadata") -@Convert(Metadata.MetadataConverter.class) -public class Metadata { - Map map; - - public Metadata() {} - - public Metadata(@Nonnull Map map) { - this.map = Utils.unmodifiableMap(Objects.requireNonNull(map, "Metadata must not be null")); - } - - public Map get() { - return map; - } - - /** XML converter class. */ - public static class MetadataConverter implements Converter { - @Override - public Metadata read(InputNode node) throws Exception { - Map map = new HashMap<>(); - while (true) { - InputNode childNode = node.getNext(); - if (childNode == null) { - break; - } - - map.put(childNode.getName(), childNode.getValue()); - } - - if (map.size() > 0) { - return new Metadata(map); - } - - return null; - } - - @Override - public void write(OutputNode node, Metadata metadata) throws Exception { - for (Map.Entry entry : metadata.get().entrySet()) { - OutputNode childNode = node.getChild(entry.getKey()); - childNode.setValue(entry.getValue()); - } - - node.commit(); - } - } -} diff --git a/api/src/main/java/io/minio/messages/Metrics.java b/api/src/main/java/io/minio/messages/Metrics.java deleted file mode 100644 index af5386322..000000000 --- a/api/src/main/java/io/minio/messages/Metrics.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.messages; - -import java.util.Objects; -import javax.annotation.Nonnull; -import org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** Helper class to denote metrics information for {@link ReplicationDestination}. */ -@Root(name = "Metrics") -public class Metrics { - @Element(name = "EventThreshold") - private ReplicationTimeValue eventThreshold; - - @Element(name = "Status") - private Status status; - - public Metrics( - @Nonnull @Element(name = "EventThreshold") ReplicationTimeValue eventThreshold, - @Nonnull @Element(name = "Status") Status status) { - this.eventThreshold = - Objects.requireNonNull(eventThreshold, "Event threshold must not be null"); - this.status = Objects.requireNonNull(status, "Status must not be null"); - } - - public ReplicationTimeValue eventThreshold() { - return this.eventThreshold; - } - - public Status status() { - return this.status; - } -} diff --git a/api/src/main/java/io/minio/messages/NoncurrentVersionExpiration.java b/api/src/main/java/io/minio/messages/NoncurrentVersionExpiration.java deleted file mode 100644 index b4b66bac0..000000000 --- a/api/src/main/java/io/minio/messages/NoncurrentVersionExpiration.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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.messages; - -import org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** Helper class to denote noncurrent version expiration information for {@link LifecycleRule}. */ -@Root(name = "NoncurrentVersionExpiration") -public class NoncurrentVersionExpiration { - @Element(name = "NoncurrentDays") - private int noncurrentDays; - - @Element(name = "NewerNoncurrentVersions", required = false) - private Integer newerNoncurrentVersions; - - public NoncurrentVersionExpiration( - @Element(name = "NoncurrentDays", required = false) int noncurrentDays) { - this.noncurrentDays = noncurrentDays; - } - - public NoncurrentVersionExpiration( - @Element(name = "NoncurrentDays", required = false) int noncurrentDays, - @Element(name = "NewerNoncurrentVersions", required = false) - Integer newerNoncurrentVersions) { - this.noncurrentDays = noncurrentDays; - this.newerNoncurrentVersions = newerNoncurrentVersions; - } - - public int noncurrentDays() { - return noncurrentDays; - } - - public Integer newerNoncurrentVersions() { - return newerNoncurrentVersions; - } -} diff --git a/api/src/main/java/io/minio/messages/NoncurrentVersionTransition.java b/api/src/main/java/io/minio/messages/NoncurrentVersionTransition.java deleted file mode 100644 index ddabe5c14..000000000 --- a/api/src/main/java/io/minio/messages/NoncurrentVersionTransition.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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.messages; - -import javax.annotation.Nonnull; -import org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** Helper class to denote noncurrent version transition information for {@link LifecycleRule}. */ -@Root(name = "NoncurrentVersionTransition") -public class NoncurrentVersionTransition extends NoncurrentVersionExpiration { - @Element(name = "StorageClass") - private String storageClass; - - public NoncurrentVersionTransition( - @Element(name = "NoncurrentDays", required = false) int noncurrentDays, - @Nonnull @Element(name = "StorageClass", required = false) String storageClass) { - super(noncurrentDays, null); - if (storageClass == null || storageClass.isEmpty()) { - throw new IllegalArgumentException("StorageClass must be provided"); - } - this.storageClass = storageClass; - } - - public NoncurrentVersionTransition( - @Element(name = "NoncurrentDays", required = false) int noncurrentDays, - @Element(name = "NewerNoncurrentVersions", required = false) Integer newerNoncurrentVersions, - @Nonnull @Element(name = "StorageClass", required = false) String storageClass) { - super(noncurrentDays, newerNoncurrentVersions); - if (storageClass == null || storageClass.isEmpty()) { - throw new IllegalArgumentException("StorageClass must be provided"); - } - this.storageClass = storageClass; - } - - public String storageClass() { - return storageClass; - } -} diff --git a/api/src/main/java/io/minio/messages/NotificationCommonConfiguration.java b/api/src/main/java/io/minio/messages/NotificationCommonConfiguration.java deleted file mode 100644 index 77c70fffe..000000000 --- a/api/src/main/java/io/minio/messages/NotificationCommonConfiguration.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2017 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 java.util.Objects; -import javax.annotation.Nonnull; -import org.simpleframework.xml.Element; -import org.simpleframework.xml.ElementList; - -/** - * Helper class to denote common fields of {@link CloudFunctionConfiguration}, {@link - * QueueConfiguration} and {@link TopicConfiguration}. - */ -public abstract class NotificationCommonConfiguration { - @Element(name = "Id", required = false) - private String id; - - @ElementList(name = "Event", inline = true) - private List events; - - @Element(name = "Filter", required = false) - private Filter filter; - - public NotificationCommonConfiguration() {} - - /** Returns id. */ - public String id() { - return id; - } - - /** Sets id. */ - public void setId(String id) { - this.id = id; - } - - /** Returns events. */ - public List events() { - return Utils.unmodifiableList(events); - } - - /** Sets event. */ - public void setEvents(@Nonnull List events) { - this.events = Utils.unmodifiableList(Objects.requireNonNull(events, "Events must not be null")); - } - - /** sets filter prefix rule. */ - public void setPrefixRule(String value) throws IllegalArgumentException { - if (filter == null) { - filter = new Filter(); - } - - filter.setPrefixRule(value); - } - - /** sets filter suffix rule. */ - public void setSuffixRule(String value) throws IllegalArgumentException { - if (filter == null) { - filter = new Filter(); - } - - filter.setSuffixRule(value); - } - - /** returns filter rule list. */ - public List filterRuleList() { - return Utils.unmodifiableList(filter == null ? null : filter.filterRuleList()); - } -} diff --git a/api/src/main/java/io/minio/messages/NotificationConfiguration.java b/api/src/main/java/io/minio/messages/NotificationConfiguration.java index ad43defdb..851a5bd6f 100644 --- a/api/src/main/java/io/minio/messages/NotificationConfiguration.java +++ b/api/src/main/java/io/minio/messages/NotificationConfiguration.java @@ -17,10 +17,14 @@ package io.minio.messages; import io.minio.Utils; +import java.util.LinkedList; import java.util.List; +import java.util.Objects; +import javax.annotation.Nonnull; import org.simpleframework.xml.Element; import org.simpleframework.xml.ElementList; import org.simpleframework.xml.Namespace; +import org.simpleframework.xml.Path; import org.simpleframework.xml.Root; /** @@ -34,63 +38,271 @@ @Namespace(reference = "http://s3.amazonaws.com/doc/2006-03-01/") public class NotificationConfiguration { @ElementList(name = "CloudFunctionConfiguration", inline = true, required = false) - private List cloudFunctionConfigurationList; + private List cloudFunctionConfigurations; @ElementList(name = "QueueConfiguration", inline = true, required = false) - private List queueConfigurationList; + private List queueConfigurations; @ElementList(name = "TopicConfiguration", inline = true, required = false) - private List topicConfigurationList; + private List topicConfigurations; @Element(name = "EventBridgeConfiguration", required = false) private EventBridgeConfiguration eventBridgeConfiguration; - public NotificationConfiguration() {} + private NotificationConfiguration() {} - /** Returns cloud function configuration. */ - public List cloudFunctionConfigurationList() { - return Utils.unmodifiableList(cloudFunctionConfigurationList); + public NotificationConfiguration( + @ElementList(name = "CloudFunctionConfiguration", inline = true, required = false) + List cloudFunctionConfigurations, + @ElementList(name = "QueueConfiguration", inline = true, required = false) + List queueConfigurations, + @ElementList(name = "TopicConfiguration", inline = true, required = false) + List topicConfigurations, + @Element(name = "EventBridgeConfiguration", required = false) + EventBridgeConfiguration eventBridgeConfiguration) { + this.cloudFunctionConfigurations = cloudFunctionConfigurations; + this.queueConfigurations = queueConfigurations; + this.topicConfigurations = topicConfigurations; + this.eventBridgeConfiguration = eventBridgeConfiguration; } - /** Sets cloud function configuration list. */ - public void setCloudFunctionConfigurationList( - List cloudFunctionConfigurationList) { - this.cloudFunctionConfigurationList = - cloudFunctionConfigurationList == null - ? null - : Utils.unmodifiableList(cloudFunctionConfigurationList); + /** Returns cloud function configuration. */ + public List cloudFunctionConfigurations() { + return Utils.unmodifiableList(cloudFunctionConfigurations); } /** Returns queue configuration list. */ - public List queueConfigurationList() { - return Utils.unmodifiableList(queueConfigurationList); - } - - /** Sets queue configuration list. */ - public void setQueueConfigurationList(List queueConfigurationList) { - this.queueConfigurationList = - queueConfigurationList == null ? null : Utils.unmodifiableList(queueConfigurationList); + public List queueConfigurations() { + return Utils.unmodifiableList(queueConfigurations); } /** Returns topic configuration list. */ - public List topicConfigurationList() { - return Utils.unmodifiableList(topicConfigurationList); - } - - /** Sets topic configuration list. */ - public void setTopicConfigurationList(List topicConfigurationList) { - this.topicConfigurationList = - topicConfigurationList == null ? null : Utils.unmodifiableList(topicConfigurationList); + public List topicConfigurations() { + return Utils.unmodifiableList(topicConfigurations); } public EventBridgeConfiguration eventBridgeConfiguration() { return this.eventBridgeConfiguration; } - public void setEventBridgeConfiguration(EventBridgeConfiguration config) { - this.eventBridgeConfiguration = config; + @Override + public String toString() { + return String.format( + "NotificationConfiguration{cloudFunctionConfigurations=%s, queueConfigurations=%s," + + " topicConfigurations=%s, eventBridgeConfiguration=%s}", + Utils.stringify(cloudFunctionConfigurations), + Utils.stringify(queueConfigurations), + Utils.stringify(topicConfigurations), + Utils.stringify(eventBridgeConfiguration)); } @Root(name = "EventBridgeConfiguration") - public static class EventBridgeConfiguration {} + public static class EventBridgeConfiguration { + @Override + public String toString() { + return String.format("EventBridgeConfiguration{}"); + } + } + + public abstract static class BaseConfiguration { + @Element(name = "Id", required = false) + private String id; + + @ElementList(entry = "Event", inline = true) + private List events; + + @Element(name = "Filter", required = false) + private Filter filter; + + public BaseConfiguration(String id, @Nonnull List events, Filter filter) { + this.id = id; + this.events = + Utils.unmodifiableList(Objects.requireNonNull(events, "Events must not be null")); + this.filter = filter; + } + + public String id() { + return id; + } + + public List events() { + return Utils.unmodifiableList(events); + } + + public Filter filter() { + return filter; + } + + @Override + public String toString() { + return String.format( + "id=%s, events=%s, filter=%s", + Utils.stringify(id), Utils.stringify(events), Utils.stringify(filter)); + } + } + + @Root(name = "Filter") + public static class Filter { + @Path(value = "S3Key") + @ElementList(name = "FilterRule", inline = true) + private List rules; + + public Filter( + @Nonnull @Path(value = "S3Key") @ElementList(name = "FilterRule", inline = true) + List rules) { + Objects.requireNonNull(rules, "Filter rules must not be null"); + if (rules.size() < 1) { + throw new IllegalArgumentException("At least one rule must be provided"); + } + if (rules.size() > 2) { + throw new IllegalArgumentException("Maximum two rules must be provided"); + } + if (rules.size() == 2 && rules.get(0).name().equals(rules.get(1).name())) { + throw new IllegalArgumentException( + "Two rules '" + rules.get(0).name() + "' must not be same"); + } + this.rules = Utils.unmodifiableList(rules); + } + + public Filter(@Nonnull String prefix, @Nonnull String suffix) { + if (prefix == null && suffix == null) { + throw new IllegalArgumentException("Either prefix or suffix must be provided"); + } + List rules = new LinkedList<>(); + if (prefix != null) rules.add(FilterRule.newPrefixFilterRule(prefix)); + if (suffix != null) rules.add(FilterRule.newSuffixFilterRule(suffix)); + this.rules = Utils.unmodifiableList(rules); + } + + public List rules() { + return rules; + } + + @Override + public String toString() { + return String.format("Filter{rules=%s}", Utils.stringify(rules)); + } + } + + @Root(name = "FilterRule") + public static class FilterRule { + @Element(name = "Name") + private String name; + + @Element(name = "Value") + private String value; + + public FilterRule( + @Nonnull @Element(name = "Name") String name, + @Nonnull @Element(name = "Value") String value) { + Objects.requireNonNull(name, "Name must not be null"); + if (!"prefix".equals(name) && !"suffix".equals(name)) { + throw new IllegalArgumentException("Name must be 'prefix' or 'suffix'"); + } + Objects.requireNonNull(value, "Value must not be null"); + this.name = name; + this.value = value; + } + + public static FilterRule newPrefixFilterRule(@Nonnull String value) { + return new FilterRule("prefix", value); + } + + public static FilterRule newSuffixFilterRule(@Nonnull String value) { + return new FilterRule("suffix", value); + } + + public String name() { + return name; + } + + public String value() { + return value; + } + + @Override + public String toString() { + return String.format( + "FilterRule{name=%s, value=%s}", Utils.stringify(name), Utils.stringify(value)); + } + } + + @Root(name = "CloudFunctionConfiguration", strict = false) + public static class CloudFunctionConfiguration extends BaseConfiguration { + @Element(name = "CloudFunction") + private String cloudFunction; + + public CloudFunctionConfiguration( + @Nonnull @Element(name = "CloudFunction") String cloudFunction, + @Element(name = "Id", required = false) String id, + @Nonnull @ElementList(entry = "Event", inline = true) List events, + @Element(name = "Filter", required = false) Filter filter) { + super(id, events, filter); + this.cloudFunction = cloudFunction; + } + + /** Returns cloudFunction. */ + public String cloudFunction() { + return cloudFunction; + } + + @Override + public String toString() { + return String.format( + "CloudFunctionConfiguration{cloudFunction=%s, %s}", + Utils.stringify(cloudFunction), super.toString()); + } + } + + @Root(name = "QueueConfiguration", strict = false) + public static class QueueConfiguration extends BaseConfiguration { + @Element(name = "Queue") + private String queue; + + public QueueConfiguration( + @Nonnull @Element(name = "Queue") String queue, + @Element(name = "Id", required = false) String id, + @Nonnull @ElementList(entry = "Event", inline = true) List events, + @Element(name = "Filter", required = false) Filter filter) { + super(id, events, filter); + this.queue = queue; + } + + /** Returns queue. */ + public String queue() { + return queue; + } + + @Override + public String toString() { + return String.format( + "QueueConfiguration{queue=%s, %s}", Utils.stringify(queue), super.toString()); + } + } + + @Root(name = "TopicConfiguration", strict = false) + public static class TopicConfiguration extends BaseConfiguration { + @Element(name = "Topic") + private String topic; + + public TopicConfiguration( + @Nonnull @Element(name = "Topic") String topic, + @Element(name = "Id", required = false) String id, + @Nonnull @ElementList(entry = "Event", inline = true) List events, + @Element(name = "Filter", required = false) Filter filter) { + super(id, events, filter); + this.topic = topic; + } + + /** Returns topic. */ + public String topic() { + return topic; + } + + @Override + public String toString() { + return String.format( + "TopicConfiguration{topic=%s, %s}", Utils.stringify(topic), super.toString()); + } + } } diff --git a/api/src/main/java/io/minio/messages/NotificationRecords.java b/api/src/main/java/io/minio/messages/NotificationRecords.java index 8f0dabe8a..82d3e1b64 100644 --- a/api/src/main/java/io/minio/messages/NotificationRecords.java +++ b/api/src/main/java/io/minio/messages/NotificationRecords.java @@ -18,13 +18,16 @@ package io.minio.messages; import com.fasterxml.jackson.annotation.JsonProperty; +import io.minio.Time; import io.minio.Utils; +import java.time.ZonedDateTime; import java.util.List; +import java.util.Map; /** * Object representation of JSON format of Event - * Message Structure. + * href="http://docs.aws.amazon.com/AmazonS3/latest/dev/notification-content-structure.html">Notification + * Content Structure. */ @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value = "UwF", @@ -36,4 +39,234 @@ public class NotificationRecords { public List events() { return Utils.unmodifiableList(events); } + + @Override + public String toString() { + return String.format("NotificationRecords{events=%s}", Utils.stringify(events)); + } + + @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( + value = "UuF", + justification = "eventVersion and eventSource are available for completeness") + public static class Event { + @JsonProperty private String eventVersion; + @JsonProperty private String eventSource; + @JsonProperty private String awsRegion; + @JsonProperty private String eventName; + @JsonProperty private Identity userIdentity; + @JsonProperty private Map requestParameters; + @JsonProperty private Map responseElements; + @JsonProperty private S3 s3; + @JsonProperty private Source source; + @JsonProperty private Time.S3Time eventTime; + + public String eventVersion() { + return eventVersion; + } + + public String eventSource() { + return eventSource; + } + + public String awsRegion() { + return awsRegion; + } + + public String eventName() { + return eventName; + } + + public String userIdentity() { + return userIdentity == null ? null : userIdentity.principalId(); + } + + public Map requestParameters() { + return Utils.unmodifiableMap(requestParameters); + } + + public Map responseElements() { + return Utils.unmodifiableMap(responseElements); + } + + public Bucket bucket() { + return s3 == null ? null : s3.bucket(); + } + + public Object object() { + return s3 == null ? null : s3.object(); + } + + public Source source() { + return source; + } + + public ZonedDateTime eventTime() { + return eventTime == null ? null : eventTime.toZonedDateTime(); + } + + @Override + public String toString() { + return String.format( + "Event{eventVersion=%s, eventSource=%s, awsRegion=%s, eventName=%s, userIdentity=%s," + + " requestParameters=%s, responseElements=%s, s3=%s, source=%s, eventTime=%s}", + Utils.stringify(eventVersion), + Utils.stringify(eventSource), + Utils.stringify(awsRegion), + Utils.stringify(eventName), + Utils.stringify(userIdentity), + Utils.stringify(requestParameters), + Utils.stringify(responseElements), + Utils.stringify(s3), + Utils.stringify(source), + Utils.stringify(eventTime)); + } + + @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( + value = "UwF", + justification = "Everything in this class is initialized by JSON unmarshalling.") + public static class Identity { + @JsonProperty private String principalId; + + public String principalId() { + return principalId; + } + + @Override + public String toString() { + return String.format("Identity{principalId=%s}", Utils.stringify(principalId)); + } + } + + @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( + value = "UwF", + justification = "Everything in this class is initialized by JSON unmarshalling.") + public static class Bucket { + @JsonProperty private String name; + @JsonProperty private Identity ownerIdentity; + @JsonProperty private String arn; + + public String name() { + return name; + } + + public String ownerIdentity() { + return ownerIdentity == null ? null : ownerIdentity.principalId(); + } + + public String arn() { + return arn; + } + + @Override + public String toString() { + return String.format( + "Bucket{name=%s, ownerIdentity=%s, arn=%s}", + Utils.stringify(name), Utils.stringify(ownerIdentity), Utils.stringify(arn)); + } + } + + public static class Object { + @JsonProperty private String key; + @JsonProperty private long size; + @JsonProperty private String eTag; + @JsonProperty private String versionId; + @JsonProperty private String sequencer; + @JsonProperty private Map userMetadata; // MinIO specific extension. + + public String key() { + return key; + } + + public long size() { + return size; + } + + public String etag() { + return eTag; + } + + public String versionId() { + return versionId; + } + + public String sequencer() { + return sequencer; + } + + public Map userMetadata() { + return Utils.unmodifiableMap(userMetadata); + } + + @Override + public String toString() { + return String.format( + "Object{key=%s, size=%d, eTag=%s, versionId=%s, sequencer=%s, userMetadata=%s}", + Utils.stringify(key), + size, + Utils.stringify(eTag), + Utils.stringify(versionId), + Utils.stringify(sequencer), + Utils.stringify(userMetadata)); + } + } + + @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( + value = {"UwF", "UuF"}, + justification = + "Everything in this class is initialized by JSON unmarshalling " + + "and s3SchemaVersion/configurationId are available for completeness.") + public static class S3 { + @JsonProperty private String s3SchemaVersion; + @JsonProperty private String configurationId; + @JsonProperty private Bucket bucket; + @JsonProperty private Object object; + + public Bucket bucket() { + return bucket; + } + + public Object object() { + return object; + } + + @Override + public String toString() { + return String.format( + "S3{s3SchemaVersion=%s, configurationId=%s, bucket=%s, object=%s}", + Utils.stringify(s3SchemaVersion), + Utils.stringify(configurationId), + Utils.stringify(bucket), + Utils.stringify(object)); + } + } + + /** This is MinIO extension. */ + @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( + value = "UwF", + justification = "Everything in this class is initialized by JSON unmarshalling.") + public static class Source { + @JsonProperty private String host; + @JsonProperty private String port; + @JsonProperty private String userAgent; + + public String host() { + return host; + } + + public String port() { + return port; + } + + public String userAgent() { + return userAgent; + } + + @Override + public String toString() { + return String.format( + "Source{host=%s, port=%s, userAgent=%s}", + Utils.stringify(host), Utils.stringify(port), Utils.stringify(userAgent)); + } + } + } } diff --git a/api/src/main/java/io/minio/messages/ObjectLockConfiguration.java b/api/src/main/java/io/minio/messages/ObjectLockConfiguration.java index 463054f7f..9459293f5 100644 --- a/api/src/main/java/io/minio/messages/ObjectLockConfiguration.java +++ b/api/src/main/java/io/minio/messages/ObjectLockConfiguration.java @@ -16,9 +16,13 @@ package io.minio.messages; +import io.minio.Utils; import org.simpleframework.xml.Element; +import org.simpleframework.xml.ElementUnion; import org.simpleframework.xml.Namespace; +import org.simpleframework.xml.Path; import org.simpleframework.xml.Root; +import org.simpleframework.xml.Text; /** * Object representation of request XML of userMetadata; // MinIO specific extension. - - public String key() { - return key; - } - - public long size() { - return size; - } - - public String etag() { - return eTag; - } - - public String versionId() { - return versionId; - } - - public String sequencer() { - return sequencer; - } - - public Map userMetadata() { - return Utils.unmodifiableMap(userMetadata); - } -} diff --git a/api/src/main/java/io/minio/messages/OutputLocation.java b/api/src/main/java/io/minio/messages/OutputLocation.java deleted file mode 100644 index e2f8091a7..000000000 --- a/api/src/main/java/io/minio/messages/OutputLocation.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2021 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 java.util.Objects; -import javax.annotation.Nonnull; -import org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** Helper class to denote output location information of {@link RestoreRequest}. */ -@Root(name = "OutputLocation") -@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") -public class OutputLocation { - @Element(name = "S3") - private S3OutputLocation s3OutputLocation; - - public OutputLocation(@Nonnull S3OutputLocation s3OutputLocation) { - this.s3OutputLocation = - Objects.requireNonNull(s3OutputLocation, "S3OutputLocation must not be null"); - } -} diff --git a/api/src/main/java/io/minio/messages/OutputSerialization.java b/api/src/main/java/io/minio/messages/OutputSerialization.java index 20ebe898e..4cbc0e8a4 100644 --- a/api/src/main/java/io/minio/messages/OutputSerialization.java +++ b/api/src/main/java/io/minio/messages/OutputSerialization.java @@ -26,25 +26,79 @@ @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") public class OutputSerialization { @Element(name = "CSV", required = false) - private CsvOutputSerialization csv; + private CSV csv; @Element(name = "JSON", required = false) - private JsonOutputSerialization json; + private JSON json; + + private OutputSerialization(CSV csv, JSON json) { + this.csv = csv; + this.json = json; + } /** Constructs a new OutputSerialization object with CSV. */ - public OutputSerialization( + public static OutputSerialization newCSV( Character fieldDelimiter, Character quoteCharacter, Character quoteEscapeCharacter, QuoteFields quoteFields, Character recordDelimiter) { - this.csv = - new CsvOutputSerialization( - fieldDelimiter, quoteCharacter, quoteEscapeCharacter, quoteFields, recordDelimiter); + return new OutputSerialization( + new CSV(fieldDelimiter, quoteCharacter, quoteEscapeCharacter, quoteFields, recordDelimiter), + null); } /** Constructs a new OutputSerialization object with JSON. */ - public OutputSerialization(Character recordDelimiter) { - this.json = new JsonOutputSerialization(recordDelimiter); + public static OutputSerialization newJSON(Character recordDelimiter) { + return new OutputSerialization(null, new JSON(recordDelimiter)); + } + + @Root(name = "CSV") + @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") + public static class CSV { + @Element(name = "FieldDelimiter", required = false) + private Character fieldDelimiter; + + @Element(name = "QuoteCharacter", required = false) + private Character quoteCharacter; + + @Element(name = "QuoteEscapeCharacter", required = false) + private Character quoteEscapeCharacter; + + @Element(name = "QuoteFields", required = false) + private QuoteFields quoteFields; + + @Element(name = "RecordDelimiter", required = false) + private Character recordDelimiter; + + public CSV( + Character fieldDelimiter, + Character quoteCharacter, + Character quoteEscapeCharacter, + QuoteFields quoteFields, + Character recordDelimiter) { + this.fieldDelimiter = fieldDelimiter; + this.quoteCharacter = quoteCharacter; + this.quoteEscapeCharacter = quoteEscapeCharacter; + this.quoteFields = quoteFields; + this.recordDelimiter = recordDelimiter; + } + } + + @Root(name = "JSON") + @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") + public static class JSON { + @Element(name = "RecordDelimiter", required = false) + private Character recordDelimiter; + + public JSON(Character recordDelimiter) { + this.recordDelimiter = recordDelimiter; + } + } + + /** Indicates whether to use quotation marks around output fields. */ + public static enum QuoteFields { + ALWAYS, + ASNEEDED; } } diff --git a/api/src/main/java/io/minio/messages/Owner.java b/api/src/main/java/io/minio/messages/Owner.java index 28ffa998b..2a73b2137 100644 --- a/api/src/main/java/io/minio/messages/Owner.java +++ b/api/src/main/java/io/minio/messages/Owner.java @@ -16,6 +16,7 @@ package io.minio.messages; +import io.minio.Utils; import org.simpleframework.xml.Element; import org.simpleframework.xml.Root; @@ -43,4 +44,10 @@ public String id() { public String displayName() { return displayName; } + + @Override + public String toString() { + return String.format( + "Owner{id=%s, displayName=%s}", Utils.stringify(id), Utils.stringify(displayName)); + } } diff --git a/api/src/main/java/io/minio/messages/Part.java b/api/src/main/java/io/minio/messages/Part.java index d8aac178e..9b50c350c 100644 --- a/api/src/main/java/io/minio/messages/Part.java +++ b/api/src/main/java/io/minio/messages/Part.java @@ -16,6 +16,8 @@ package io.minio.messages; +import io.minio.Time; +import io.minio.Utils; import java.time.ZonedDateTime; import org.simpleframework.xml.Element; import org.simpleframework.xml.Root; @@ -25,7 +27,7 @@ * CompleteMultipartUpload} and {@link ListPartsResult}. */ @Root(name = "Part", strict = false) -public class Part { +public class Part extends Checksum { @Element(name = "PartNumber", required = false) private int partNumber; @@ -33,36 +35,18 @@ public class Part { private String etag; @Element(name = "LastModified", required = false) - private ResponseDate lastModified; + private Time.S3Time lastModified; @Element(name = "Size", required = false) private Long size; - @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; - public Part() {} - /** Constructs a new Part object with given part number and ETag. */ public Part(int partNumber, String etag) { - this.partNumber = partNumber; this.etag = etag; } - /** Constructs a new Part object with given values. */ public Part( int partNumber, String etag, @@ -71,52 +55,35 @@ public Part( String checksumCRC64NVME, String checksumSHA1, String checksumSHA256) { + super(checksumCRC32, checksumCRC32C, checksumCRC64NVME, checksumSHA1, checksumSHA256, null); this.partNumber = partNumber; this.etag = etag; - this.checksumCRC32 = checksumCRC32; - this.checksumCRC32C = checksumCRC32C; - this.checksumCRC64NVME = checksumCRC64NVME; - this.checksumSHA1 = checksumSHA1; - this.checksumSHA256 = checksumSHA256; } - /** Returns part number. */ public int partNumber() { return partNumber; } - /** Returns ETag. */ public String etag() { return etag.replaceAll("\"", ""); } - /** Returns last modified time. */ public ZonedDateTime lastModified() { - return lastModified.zonedDateTime(); + return lastModified == null ? null : lastModified.toZonedDateTime(); } - /** Returns part size. */ public long partSize() { return size; } - 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; + @Override + public String toString() { + return String.format( + "Part{partNumber=%s, etag=%s, lastModified=%s, size=%s, %s}", + Utils.stringify(partNumber), + Utils.stringify(etag), + Utils.stringify(lastModified), + Utils.stringify(size), + super.stringify()); } } diff --git a/api/src/main/java/io/minio/messages/Permission.java b/api/src/main/java/io/minio/messages/Permission.java deleted file mode 100644 index a86d43007..000000000 --- a/api/src/main/java/io/minio/messages/Permission.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2021 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 org.simpleframework.xml.Root; - -/** Permission represents type of grantee. */ -@Root(name = "Permission") -public enum Permission { - FULL_CONTROL, - WRITE, - WRITE_ACP, - READ, - READ_ACP; -} diff --git a/api/src/main/java/io/minio/messages/Prefix.java b/api/src/main/java/io/minio/messages/Prefix.java deleted file mode 100644 index 4a27a7bc4..000000000 --- a/api/src/main/java/io/minio/messages/Prefix.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2015 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 org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** - * Helper class to denote Prefix information in {@link ListBucketResultV1}, {@link - * ListBucketResultV2} and {@link ListVersionsResult}. - */ -@Root(name = "CommonPrefixes", strict = false) -public class Prefix { - @Element(name = "Prefix") - private String prefix; - - public Prefix() {} - - public Item toItem() { - return new Contents(prefix); - } -} diff --git a/api/src/main/java/io/minio/messages/Progress.java b/api/src/main/java/io/minio/messages/Progress.java index 71ba29590..32558a272 100644 --- a/api/src/main/java/io/minio/messages/Progress.java +++ b/api/src/main/java/io/minio/messages/Progress.java @@ -16,10 +16,23 @@ package io.minio.messages; +import org.simpleframework.xml.Element; import org.simpleframework.xml.Namespace; import org.simpleframework.xml.Root; /** Helper class to denote Progress information of S3 select response message. */ @Root(name = "Progress", strict = false) @Namespace(reference = "http://s3.amazonaws.com/doc/2006-03-01/") -public class Progress extends Stats {} +public class Progress extends Stats { + public Progress( + @Element(name = "BytesScanned", required = false) Long bytesScanned, + @Element(name = "BytesProcessed", required = false) Long bytesProcessed, + @Element(name = "BytesReturned", required = false) Long bytesReturned) { + super(bytesScanned, bytesProcessed, bytesReturned); + } + + @Override + public String toString() { + return String.format("Progress{%s}", super.stringify()); + } +} diff --git a/api/src/main/java/io/minio/messages/QueueConfiguration.java b/api/src/main/java/io/minio/messages/QueueConfiguration.java deleted file mode 100644 index 2123b6505..000000000 --- a/api/src/main/java/io/minio/messages/QueueConfiguration.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2017 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 org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** Helper class to denote Queue configuration of {@link NotificationConfiguration}. */ -@Root(name = "QueueConfiguration", strict = false) -public class QueueConfiguration extends NotificationCommonConfiguration { - @Element(name = "Queue") - private String queue; - - public QueueConfiguration() { - super(); - } - - /** Returns queue. */ - public String queue() { - return queue; - } - - /** Sets queue. */ - public void setQueue(String queue) { - this.queue = queue; - } -} diff --git a/api/src/main/java/io/minio/messages/QuoteFields.java b/api/src/main/java/io/minio/messages/QuoteFields.java deleted file mode 100644 index a33e1871f..000000000 --- a/api/src/main/java/io/minio/messages/QuoteFields.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2019 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; - -/** Indicates whether to use quotation marks around output fields. */ -public enum QuoteFields { - ALWAYS, - ASNEEDED; -} diff --git a/api/src/main/java/io/minio/messages/ReplicaModifications.java b/api/src/main/java/io/minio/messages/ReplicaModifications.java deleted file mode 100644 index dc18cf9ed..000000000 --- a/api/src/main/java/io/minio/messages/ReplicaModifications.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2022 MinIO, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.minio.messages; - -import java.util.Objects; -import javax.annotation.Nonnull; -import org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** Helper class to denote replica modifications information for {@link SourceSelectionCriteria}. */ -@Root(name = "ReplicaModifications") -public class ReplicaModifications { - @Element(name = "Status") - private Status status; - - public ReplicaModifications(@Nonnull @Element(name = "Status") Status status) { - this.status = Objects.requireNonNull(status, "Status must not be null"); - } - - public Status status() { - return this.status; - } -} diff --git a/api/src/main/java/io/minio/messages/ReplicationConfiguration.java b/api/src/main/java/io/minio/messages/ReplicationConfiguration.java index 3dcb87629..e0e9485b2 100644 --- a/api/src/main/java/io/minio/messages/ReplicationConfiguration.java +++ b/api/src/main/java/io/minio/messages/ReplicationConfiguration.java @@ -25,6 +25,7 @@ import org.simpleframework.xml.ElementList; import org.simpleframework.xml.Namespace; import org.simpleframework.xml.Root; +import org.simpleframework.xml.convert.Convert; /** * Object representation of request XML of rules; + private List rules; - /** Constructs new replication configuration. */ public ReplicationConfiguration( @Nullable @Element(name = "Role", required = false) String role, - @Nonnull @ElementList(name = "Rule", inline = true) List rules) { + @Nonnull @ElementList(name = "Rule", inline = true) List rules) { this.role = role; // Role is not applicable in MinIO server and it is optional. this.rules = Utils.unmodifiableList(Objects.requireNonNull(rules, "Rules must not be null")); @@ -61,7 +61,513 @@ public String role() { return role; } - public List rules() { + public List rules() { return Utils.unmodifiableList(rules); } + + @Override + public String toString() { + return String.format( + "ReplicationConfiguration{role=%s, rules=%s}", + Utils.stringify(role), Utils.stringify(rules)); + } + + @Root(name = "Rule") + public static class Rule { + @Element(name = "DeleteMarkerReplication", required = false) + private DeleteMarkerReplication deleteMarkerReplication; + + @Element(name = "Destination") + private Destination destination; + + @Element(name = "ExistingObjectReplication", required = false) + private ExistingObjectReplication existingObjectReplication; + + @Element(name = "Filter", required = false) + private Filter filter; + + @Element(name = "ID", required = false) + private String id; + + @Element(name = "Prefix", required = false) + @Convert(PrefixConverter.class) + private String prefix; + + @Element(name = "Priority", required = false) + private Integer priority; + + @Element(name = "SourceSelectionCriteria", required = false) + private SourceSelectionCriteria sourceSelectionCriteria; + + @Element(name = "DeleteReplication", required = false) + private DeleteReplication deleteReplication; // This is MinIO specific extension. + + @Element(name = "Status") + private Status status; + + public Rule( + @Nullable @Element(name = "DeleteMarkerReplication", required = false) + DeleteMarkerReplication deleteMarkerReplication, + @Nonnull @Element(name = "Destination") Destination destination, + @Nullable @Element(name = "ExistingObjectReplication", required = false) + ExistingObjectReplication existingObjectReplication, + @Nullable @Element(name = "Filter", required = false) Filter filter, + @Nullable @Element(name = "ID", required = false) String id, + @Nullable @Element(name = "Prefix", required = false) String prefix, + @Nullable @Element(name = "Priority", required = false) Integer priority, + @Nullable @Element(name = "SourceSelectionCriteria", required = false) + SourceSelectionCriteria sourceSelectionCriteria, + @Nullable @Element(name = "DeleteReplication", required = false) + DeleteReplication deleteReplication, + @Nonnull @Element(name = "Status") Status status) { + + if (filter != null && deleteMarkerReplication == null) { + deleteMarkerReplication = new DeleteMarkerReplication(null); + } + + if (id != null) { + id = id.trim(); + if (id.isEmpty()) throw new IllegalArgumentException("ID must be non-empty string"); + if (id.length() > 255) + throw new IllegalArgumentException("ID must be exceed 255 characters"); + } + + this.deleteMarkerReplication = deleteMarkerReplication; + this.destination = Objects.requireNonNull(destination, "Destination must not be null"); + this.existingObjectReplication = existingObjectReplication; + this.filter = filter; + this.id = id; + this.prefix = prefix; + this.priority = priority; + this.sourceSelectionCriteria = sourceSelectionCriteria; + this.deleteReplication = deleteReplication; + this.status = Objects.requireNonNull(status, "Status must not be null"); + } + + public Rule( + @Nullable @Element(name = "DeleteMarkerReplication", required = false) + DeleteMarkerReplication deleteMarkerReplication, + @Nonnull @Element(name = "Destination") Destination destination, + @Nullable @Element(name = "ExistingObjectReplication", required = false) + ExistingObjectReplication existingObjectReplication, + @Nullable @Element(name = "Filter", required = false) Filter filter, + @Nullable @Element(name = "ID", required = false) String id, + @Nullable @Element(name = "Prefix", required = false) String prefix, + @Nullable @Element(name = "Priority", required = false) Integer priority, + @Nullable @Element(name = "SourceSelectionCriteria", required = false) + SourceSelectionCriteria sourceSelectionCriteria, + @Nonnull @Element(name = "Status") Status status) { + this( + deleteMarkerReplication, + destination, + existingObjectReplication, + filter, + id, + prefix, + priority, + sourceSelectionCriteria, + null, + status); + } + + public DeleteMarkerReplication deleteMarkerReplication() { + return this.deleteMarkerReplication; + } + + public Destination destination() { + return this.destination; + } + + public ExistingObjectReplication existingObjectReplication() { + return this.existingObjectReplication; + } + + public Filter filter() { + return this.filter; + } + + public String id() { + return this.id; + } + + public String prefix() { + return this.prefix; + } + + public Integer priority() { + return this.priority; + } + + public SourceSelectionCriteria sourceSelectionCriteria() { + return this.sourceSelectionCriteria; + } + + public DeleteReplication deleteReplication() { + return this.deleteReplication; + } + + public Status status() { + return this.status; + } + + @Override + public String toString() { + return String.format( + "Rule{deleteMarkerReplication=%s, destination=%s, existingObjectReplication=%s," + + " filter=%s, id=%s, prefix=%s, priority=%s, sourceSelectionCriteria=%s," + + " deleteReplication=%s, status=%s}", + Utils.stringify(deleteMarkerReplication), + Utils.stringify(destination), + Utils.stringify(existingObjectReplication), + Utils.stringify(filter), + Utils.stringify(id), + Utils.stringify(prefix), + Utils.stringify(priority), + Utils.stringify(sourceSelectionCriteria), + Utils.stringify(deleteReplication), + Utils.stringify(status)); + } + } + + @Root(name = "DeleteMarkerReplication") + public static class DeleteMarkerReplication { + @Element(name = "Status", required = false) + private Status status; + + public DeleteMarkerReplication( + @Nullable @Element(name = "Status", required = false) Status status) { + this.status = (status == null) ? Status.DISABLED : status; + } + + public Status status() { + return status; + } + + @Override + public String toString() { + return String.format("DeleteMarkerReplication{status=%s}", Utils.stringify(status)); + } + } + + @Root(name = "Destination") + public static class Destination { + @Element(name = "AccessControlTranslation", required = false) + private AccessControlTranslation accessControlTranslation; + + @Element(name = "Account", required = false) + private String account; + + @Element(name = "Bucket") + private String bucketArn; + + @Element(name = "EncryptionConfiguration", required = false) + private EncryptionConfiguration encryptionConfiguration; + + @Element(name = "Metrics", required = false) + private Metrics metrics; + + @Element(name = "ReplicationTime", required = false) + private ReplicationTime replicationTime; + + @Element(name = "StorageClass", required = false) + private String storageClass; + + public Destination( + @Nullable @Element(name = "AccessControlTranslation", required = false) + AccessControlTranslation accessControlTranslation, + @Nullable @Element(name = "Account", required = false) String account, + @Nonnull @Element(name = "Bucket") String bucketArn, + @Nullable @Element(name = "EncryptionConfiguration", required = false) + EncryptionConfiguration encryptionConfiguration, + @Nullable @Element(name = "Metrics", required = false) Metrics metrics, + @Nullable @Element(name = "ReplicationTime", required = false) + ReplicationTime replicationTime, + @Nullable @Element(name = "StorageClass", required = false) String storageClass) { + this.accessControlTranslation = accessControlTranslation; + this.account = account; + this.bucketArn = Objects.requireNonNull(bucketArn, "Bucket ARN must not be null"); + this.encryptionConfiguration = encryptionConfiguration; + this.metrics = metrics; + this.replicationTime = replicationTime; + this.storageClass = storageClass; + } + + public AccessControlTranslation accessControlTranslation() { + return this.accessControlTranslation; + } + + public String account() { + return this.account; + } + + public String bucketArn() { + return this.bucketArn; + } + + public EncryptionConfiguration encryptionConfiguration() { + return encryptionConfiguration; + } + + public Metrics metrics() { + return this.metrics; + } + + public ReplicationTime replicationTime() { + return this.replicationTime; + } + + public String storageClass() { + return this.storageClass; + } + + @Override + public String toString() { + return String.format( + "Destination{accessControlTranslation=%s, account=%s, bucketArn=%s," + + " encryptionConfiguration=%s, metrics=%s, replicationTime=%s, storageClass=%s}", + Utils.stringify(accessControlTranslation), + Utils.stringify(account), + Utils.stringify(bucketArn), + Utils.stringify(encryptionConfiguration), + Utils.stringify(metrics), + Utils.stringify(replicationTime), + Utils.stringify(storageClass)); + } + } + + @Root(name = "AccessControlTranslation") + public static class AccessControlTranslation { + @Element(name = "Owner") + private String owner = "Destination"; + + public AccessControlTranslation(@Nonnull @Element(name = "Owner") String owner) { + this.owner = Objects.requireNonNull(owner, "Owner must not be null"); + } + + public String owner() { + return this.owner; + } + + @Override + public String toString() { + return String.format("AccessControlTranslation{owner=%s}", Utils.stringify(owner)); + } + } + + @Root(name = "EncryptionConfiguration") + public static class EncryptionConfiguration { + @Element(name = "ReplicaKmsKeyID", required = false) + private String replicaKmsKeyID; + + public EncryptionConfiguration( + @Nullable @Element(name = "ReplicaKmsKeyID", required = false) String replicaKmsKeyID) { + this.replicaKmsKeyID = replicaKmsKeyID; + } + + public String replicaKmsKeyID() { + return this.replicaKmsKeyID; + } + + @Override + public String toString() { + return String.format( + "EncryptionConfiguration{replicaKmsKeyID=%s}", Utils.stringify(replicaKmsKeyID)); + } + } + + @Root(name = "Metrics") + public static class Metrics { + @Element(name = "EventThreshold") + private ReplicationTimeValue eventThreshold; + + @Element(name = "Status") + private Status status; + + public Metrics( + @Nonnull @Element(name = "EventThreshold") ReplicationTimeValue eventThreshold, + @Nonnull @Element(name = "Status") Status status) { + this.eventThreshold = + Objects.requireNonNull(eventThreshold, "Event threshold must not be null"); + this.status = Objects.requireNonNull(status, "Status must not be null"); + } + + public ReplicationTimeValue eventThreshold() { + return this.eventThreshold; + } + + public Status status() { + return this.status; + } + + @Override + public String toString() { + return String.format( + "Metrics{eventThreshold=%s, status=%s}", + Utils.stringify(eventThreshold), Utils.stringify(status)); + } + } + + @Root(name = "ReplicationTime") + public static class ReplicationTime { + @Element(name = "Time") + private ReplicationTimeValue time; + + @Element(name = "Status") + private Status status; + + public ReplicationTime( + @Nonnull @Element(name = "Time") ReplicationTimeValue time, + @Nonnull @Element(name = "Status") Status status) { + this.time = Objects.requireNonNull(time, "Time must not be null"); + this.status = Objects.requireNonNull(status, "Status must not be null"); + } + + public ReplicationTimeValue time() { + return this.time; + } + + public Status status() { + return this.status; + } + + @Override + public String toString() { + return String.format( + "ReplicationTime{time=%s, status=%s}", Utils.stringify(time), Utils.stringify(status)); + } + } + + @Root(name = "ReplicationTimeValue") + public static class ReplicationTimeValue { + @Element(name = "Minutes", required = false) + private Integer minutes = 15; + + public ReplicationTimeValue( + @Nullable @Element(name = "Minutes", required = false) Integer minutes) { + this.minutes = minutes; + } + + public Integer minutes() { + return this.minutes; + } + + @Override + public String toString() { + return String.format("ReplicationTimeValue{minutes=%s}", Utils.stringify(minutes)); + } + } + + @Root(name = "ExistingObjectReplication") + public static class ExistingObjectReplication { + @Element(name = "Status") + private Status status; + + public ExistingObjectReplication(@Nonnull @Element(name = "Status") Status status) { + this.status = Objects.requireNonNull(status, "Status must not be null"); + } + + public Status status() { + return this.status; + } + + @Override + public String toString() { + return String.format("ExistingObjectReplication{status=%s}", Utils.stringify(status)); + } + } + + @Root(name = "SourceSelectionCriteria") + public static class SourceSelectionCriteria { + @Element(name = "ReplicaModifications", required = false) + private ReplicaModifications replicaModifications; + + @Element(name = "SseKmsEncryptedObjects", required = false) + private SseKmsEncryptedObjects sseKmsEncryptedObjects; + + public SourceSelectionCriteria( + @Nullable @Element(name = "SseKmsEncryptedObjects", required = false) + SseKmsEncryptedObjects sseKmsEncryptedObjects, + @Nullable @Element(name = "ReplicaModifications", required = false) + ReplicaModifications replicaModifications) { + this.sseKmsEncryptedObjects = sseKmsEncryptedObjects; + this.replicaModifications = replicaModifications; + } + + public SourceSelectionCriteria(@Nullable SseKmsEncryptedObjects sseKmsEncryptedObjects) { + this(sseKmsEncryptedObjects, null); + } + + public ReplicaModifications replicaModifications() { + return this.replicaModifications; + } + + public SseKmsEncryptedObjects sseKmsEncryptedObjects() { + return this.sseKmsEncryptedObjects; + } + + @Override + public String toString() { + return String.format( + "SourceSelectionCriteria{replicaModifications=%s, sseKmsEncryptedObjects=%s}", + Utils.stringify(replicaModifications), Utils.stringify(sseKmsEncryptedObjects)); + } + } + + @Root(name = "ReplicaModifications") + public static class ReplicaModifications { + @Element(name = "Status") + private Status status; + + public ReplicaModifications(@Nonnull @Element(name = "Status") Status status) { + this.status = Objects.requireNonNull(status, "Status must not be null"); + } + + public Status status() { + return this.status; + } + + @Override + public String toString() { + return String.format("ReplicaModifications{status=%s}", Utils.stringify(status)); + } + } + + @Root(name = "SseKmsEncryptedObjects") + public static class SseKmsEncryptedObjects { + @Element(name = "Status") + private Status status; + + public SseKmsEncryptedObjects(@Nonnull @Element(name = "Status") Status status) { + this.status = Objects.requireNonNull(status, "Status must not be null"); + } + + public Status status() { + return this.status; + } + + @Override + public String toString() { + return String.format("SseKmsEncryptedObjects{status=%s}", Utils.stringify(status)); + } + } + + /** This is MinIO specific extension. */ + @Root(name = "DeleteReplication") + public static class DeleteReplication { + @Element(name = "Status", required = false) + private Status status; + + public DeleteReplication(@Nullable @Element(name = "Status", required = false) Status status) { + this.status = (status == null) ? Status.DISABLED : status; + } + + public Status status() { + return status; + } + + @Override + public String toString() { + return String.format("DeleteReplication{status=%s}", Utils.stringify(status)); + } + } } diff --git a/api/src/main/java/io/minio/messages/ReplicationDestination.java b/api/src/main/java/io/minio/messages/ReplicationDestination.java deleted file mode 100644 index 2a308d639..000000000 --- a/api/src/main/java/io/minio/messages/ReplicationDestination.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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.messages; - -import java.util.Objects; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** Helper class to denote Destination information for {@link ReplicationRule}. */ -@Root(name = "Destination") -public class ReplicationDestination { - @Element(name = "AccessControlTranslation", required = false) - private AccessControlTranslation accessControlTranslation; - - @Element(name = "Account", required = false) - private String account; - - @Element(name = "Bucket") - private String bucketArn; - - @Element(name = "EncryptionConfiguration", required = false) - private EncryptionConfiguration encryptionConfiguration; - - @Element(name = "Metrics", required = false) - private Metrics metrics; - - @Element(name = "ReplicationTime", required = false) - private ReplicationTime replicationTime; - - @Element(name = "StorageClass", required = false) - private String storageClass; - - public ReplicationDestination( - @Nullable @Element(name = "AccessControlTranslation", required = false) - AccessControlTranslation accessControlTranslation, - @Nullable @Element(name = "Account", required = false) String account, - @Nonnull @Element(name = "Bucket") String bucketArn, - @Nullable @Element(name = "EncryptionConfiguration", required = false) - EncryptionConfiguration encryptionConfiguration, - @Nullable @Element(name = "Metrics", required = false) Metrics metrics, - @Nullable @Element(name = "ReplicationTime", required = false) - ReplicationTime replicationTime, - @Nullable @Element(name = "StorageClass", required = false) String storageClass) { - this.accessControlTranslation = accessControlTranslation; - this.account = account; - this.bucketArn = Objects.requireNonNull(bucketArn, "Bucket ARN must not be null"); - this.encryptionConfiguration = encryptionConfiguration; - this.metrics = metrics; - this.replicationTime = replicationTime; - this.storageClass = storageClass; - } - - public AccessControlTranslation accessControlTranslation() { - return this.accessControlTranslation; - } - - public String account() { - return this.account; - } - - public String bucketArn() { - return this.bucketArn; - } - - public EncryptionConfiguration encryptionConfiguration() { - return encryptionConfiguration; - } - - public Metrics metrics() { - return this.metrics; - } - - public ReplicationTime replicationTime() { - return this.replicationTime; - } - - public String storageClass() { - return this.storageClass; - } -} diff --git a/api/src/main/java/io/minio/messages/ReplicationRule.java b/api/src/main/java/io/minio/messages/ReplicationRule.java deleted file mode 100644 index 75993da97..000000000 --- a/api/src/main/java/io/minio/messages/ReplicationRule.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * 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.messages; - -import java.util.Objects; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; -import org.simpleframework.xml.convert.Convert; - -/** Helper class to denote Rule information for {@link ReplicationConfiguration}. */ -@Root(name = "Rule") -public class ReplicationRule { - @Element(name = "DeleteMarkerReplication", required = false) - private DeleteMarkerReplication deleteMarkerReplication; - - @Element(name = "Destination") - private ReplicationDestination destination; - - @Element(name = "ExistingObjectReplication", required = false) - private ExistingObjectReplication existingObjectReplication; - - @Element(name = "Filter", required = false) - private RuleFilter filter; - - @Element(name = "ID", required = false) - private String id; - - @Element(name = "Prefix", required = false) - @Convert(PrefixConverter.class) - private String prefix; - - @Element(name = "Priority", required = false) - private Integer priority; - - @Element(name = "SourceSelectionCriteria", required = false) - private SourceSelectionCriteria sourceSelectionCriteria; - - @Element(name = "DeleteReplication", required = false) - private DeleteReplication deleteReplication; // This is MinIO specific extension. - - @Element(name = "Status") - private Status status; - - /** Constructs new server-side encryption configuration rule. */ - public ReplicationRule( - @Nullable @Element(name = "DeleteMarkerReplication", required = false) - DeleteMarkerReplication deleteMarkerReplication, - @Nonnull @Element(name = "Destination") ReplicationDestination destination, - @Nullable @Element(name = "ExistingObjectReplication", required = false) - ExistingObjectReplication existingObjectReplication, - @Nullable @Element(name = "Filter", required = false) RuleFilter filter, - @Nullable @Element(name = "ID", required = false) String id, - @Nullable @Element(name = "Prefix", required = false) String prefix, - @Nullable @Element(name = "Priority", required = false) Integer priority, - @Nullable @Element(name = "SourceSelectionCriteria", required = false) - SourceSelectionCriteria sourceSelectionCriteria, - @Nullable @Element(name = "DeleteReplication", required = false) - DeleteReplication deleteReplication, - @Nonnull @Element(name = "Status") Status status) { - - if (filter != null && deleteMarkerReplication == null) { - deleteMarkerReplication = new DeleteMarkerReplication(null); - } - - if (id != null) { - id = id.trim(); - if (id.isEmpty()) throw new IllegalArgumentException("ID must be non-empty string"); - if (id.length() > 255) throw new IllegalArgumentException("ID must be exceed 255 characters"); - } - - this.deleteMarkerReplication = deleteMarkerReplication; - this.destination = Objects.requireNonNull(destination, "Destination must not be null"); - this.existingObjectReplication = existingObjectReplication; - this.filter = filter; - this.id = id; - this.prefix = prefix; - this.priority = priority; - this.sourceSelectionCriteria = sourceSelectionCriteria; - this.deleteReplication = deleteReplication; - this.status = Objects.requireNonNull(status, "Status must not be null"); - } - - /** Constructs new server-side encryption configuration rule. */ - public ReplicationRule( - @Nullable @Element(name = "DeleteMarkerReplication", required = false) - DeleteMarkerReplication deleteMarkerReplication, - @Nonnull @Element(name = "Destination") ReplicationDestination destination, - @Nullable @Element(name = "ExistingObjectReplication", required = false) - ExistingObjectReplication existingObjectReplication, - @Nullable @Element(name = "Filter", required = false) RuleFilter filter, - @Nullable @Element(name = "ID", required = false) String id, - @Nullable @Element(name = "Prefix", required = false) String prefix, - @Nullable @Element(name = "Priority", required = false) Integer priority, - @Nullable @Element(name = "SourceSelectionCriteria", required = false) - SourceSelectionCriteria sourceSelectionCriteria, - @Nonnull @Element(name = "Status") Status status) { - this( - deleteMarkerReplication, - destination, - existingObjectReplication, - filter, - id, - prefix, - priority, - sourceSelectionCriteria, - null, - status); - } - - public DeleteMarkerReplication deleteMarkerReplication() { - return this.deleteMarkerReplication; - } - - public ReplicationDestination destination() { - return this.destination; - } - - public ExistingObjectReplication existingObjectReplication() { - return this.existingObjectReplication; - } - - public RuleFilter filter() { - return this.filter; - } - - public String id() { - return this.id; - } - - public String prefix() { - return this.prefix; - } - - public Integer priority() { - return this.priority; - } - - public SourceSelectionCriteria sourceSelectionCriteria() { - return this.sourceSelectionCriteria; - } - - public DeleteReplication deleteReplication() { - return this.deleteReplication; - } - - public Status status() { - return this.status; - } -} diff --git a/api/src/main/java/io/minio/messages/ReplicationTime.java b/api/src/main/java/io/minio/messages/ReplicationTime.java deleted file mode 100644 index 9ba40cadb..000000000 --- a/api/src/main/java/io/minio/messages/ReplicationTime.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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.messages; - -import java.util.Objects; -import javax.annotation.Nonnull; -import org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** Helper class to denote replication time information for {@link ReplicationDestination}. */ -@Root(name = "ReplicationTime") -public class ReplicationTime { - @Element(name = "Time") - private ReplicationTimeValue time; - - @Element(name = "Status") - private Status status; - - public ReplicationTime( - @Nonnull @Element(name = "Time") ReplicationTimeValue time, - @Nonnull @Element(name = "Status") Status status) { - this.time = Objects.requireNonNull(time, "Time must not be null"); - this.status = Objects.requireNonNull(status, "Status must not be null"); - } - - public ReplicationTimeValue time() { - return this.time; - } - - public Status status() { - return this.status; - } -} diff --git a/api/src/main/java/io/minio/messages/ReplicationTimeValue.java b/api/src/main/java/io/minio/messages/ReplicationTimeValue.java deleted file mode 100644 index 41886278c..000000000 --- a/api/src/main/java/io/minio/messages/ReplicationTimeValue.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.messages; - -import javax.annotation.Nullable; -import org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** Helper class to denote replication time value information for {@link Metrics}. */ -@Root(name = "ReplicationTimeValue") -public class ReplicationTimeValue { - @Element(name = "Minutes", required = false) - private Integer minutes = 15; - - public ReplicationTimeValue( - @Nullable @Element(name = "Minutes", required = false) Integer minutes) { - this.minutes = minutes; - } - - public Integer minutes() { - return this.minutes; - } -} diff --git a/api/src/main/java/io/minio/messages/RequestProgress.java b/api/src/main/java/io/minio/messages/RequestProgress.java deleted file mode 100644 index 1f3eb9c7c..000000000 --- a/api/src/main/java/io/minio/messages/RequestProgress.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2019 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 org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** - * Helper class to denote progress request in select object content request XML for {@link - * SelectObjectContentRequest}. - */ -@Root(name = "RequestProgress", strict = false) -@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") -public class RequestProgress { - @Element(name = "Enabled") - private boolean enabled = true; - - /** Constructs a new RequestProgress object. */ - public RequestProgress() {} -} diff --git a/api/src/main/java/io/minio/messages/ResponseDate.java b/api/src/main/java/io/minio/messages/ResponseDate.java deleted file mode 100644 index 9e3f426ac..000000000 --- a/api/src/main/java/io/minio/messages/ResponseDate.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2015 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.fasterxml.jackson.annotation.JsonCreator; -import io.minio.Time; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; -import java.util.Locale; -import org.simpleframework.xml.Root; -import org.simpleframework.xml.convert.Convert; -import org.simpleframework.xml.convert.Converter; -import org.simpleframework.xml.stream.InputNode; -import org.simpleframework.xml.stream.OutputNode; - -/** S3 specified response time wrapping {@link ZonedDateTime}. */ -@Root -@Convert(ResponseDate.ResponseDateConverter.class) -public class ResponseDate { - public static final DateTimeFormatter MINIO_RESPONSE_DATE_FORMAT = - DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH':'mm':'ss'Z'", Locale.US).withZone(Time.UTC); - - private ZonedDateTime zonedDateTime; - - public ResponseDate() {} - - public ResponseDate(ZonedDateTime zonedDateTime) { - this.zonedDateTime = zonedDateTime; - } - - public ZonedDateTime zonedDateTime() { - return zonedDateTime; - } - - public String toString() { - return zonedDateTime.format(Time.RESPONSE_DATE_FORMAT); - } - - @JsonCreator - public static ResponseDate fromString(String responseDateString) { - try { - return new ResponseDate(ZonedDateTime.parse(responseDateString, Time.RESPONSE_DATE_FORMAT)); - } catch (DateTimeParseException e) { - return new ResponseDate(ZonedDateTime.parse(responseDateString, MINIO_RESPONSE_DATE_FORMAT)); - } - } - - /** XML converter class. */ - public static class ResponseDateConverter implements Converter { - @Override - public ResponseDate read(InputNode node) throws Exception { - return ResponseDate.fromString(node.getValue()); - } - - @Override - public void write(OutputNode node, ResponseDate amzDate) { - node.setValue(amzDate.toString()); - } - } -} diff --git a/api/src/main/java/io/minio/messages/RestoreRequest.java b/api/src/main/java/io/minio/messages/RestoreRequest.java index a7718604e..429f08905 100644 --- a/api/src/main/java/io/minio/messages/RestoreRequest.java +++ b/api/src/main/java/io/minio/messages/RestoreRequest.java @@ -16,10 +16,20 @@ package io.minio.messages; +import com.fasterxml.jackson.annotation.JsonCreator; +import io.minio.Utils; +import java.util.Map; +import java.util.Objects; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.simpleframework.xml.Element; +import org.simpleframework.xml.ElementMap; import org.simpleframework.xml.Namespace; import org.simpleframework.xml.Root; +import org.simpleframework.xml.convert.Convert; +import org.simpleframework.xml.convert.Converter; +import org.simpleframework.xml.stream.InputNode; +import org.simpleframework.xml.stream.OutputNode; /** * Object representation of request XML of { + @Override + public Tier read(InputNode node) throws Exception { + return Tier.fromString(node.getValue()); + } + + @Override + public void write(OutputNode node, Tier tier) throws Exception { + node.setValue(tier.toString()); + } + } + } + + @Root(name = "GlacierJobParameters") + @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") + public static class GlacierJobParameters { + @Element(name = "Tier") + private Tier tier; + + public GlacierJobParameters(@Nonnull Tier tier) { + this.tier = Objects.requireNonNull(tier, "Tier must not be null"); + } + } + + @Root(name = "SelectParameters") + @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") + public static class SelectParameters extends BaseSelectParameters { + public SelectParameters( + @Nonnull String expression, + @Nonnull InputSerialization is, + @Nonnull OutputSerialization os) { + super(expression, is, os); + } + } + + @Root(name = "OutputLocation") + @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") + public static class OutputLocation { + @Element(name = "S3") + private S3 s3; + + public OutputLocation(@Nonnull S3 s3) { + this.s3 = Objects.requireNonNull(s3, "S3 must not be null"); + } + } + + @Root(name = "S3") + @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") + public static class S3 { + @Element(name = "AccessControlList", required = false) + private AccessControlList accessControlList; + + @Element(name = "BucketName") + private String bucketName; + + @Element(name = "CannedACL", required = false) + private CannedAcl cannedAcl; + + @Element(name = "Encryption", required = false) + private Encryption encryption; + + @Element(name = "Prefix") + private String prefix; + + @Element(name = "StorageClass", required = false) + private String storageClass; + + @Element(name = "Tagging", required = false) + private Tags tagging; + + @Element(name = "UserMetadata", required = false) + private UserMetadata userMetadata; + + public S3( + @Nonnull String bucketName, + @Nonnull String prefix, + @Nullable AccessControlList accessControlList, + @Nullable CannedAcl cannedAcl, + @Nullable Encryption encryption, + @Nullable String storageClass, + @Nullable Tags tagging, + @Nullable UserMetadata userMetadata) { + this.bucketName = Objects.requireNonNull(bucketName, "Bucket name must not be null"); + this.prefix = Objects.requireNonNull(prefix, "Prefix must not be null"); + this.accessControlList = accessControlList; + this.cannedAcl = cannedAcl; + this.encryption = encryption; + this.storageClass = storageClass; + this.tagging = tagging; + this.userMetadata = userMetadata; + } + } + + @Root(name = "CannedAcl") + @Convert(CannedAcl.CannedAclConverter.class) + public static enum CannedAcl { + PRIVATE("private"), + PUBLIC_READ("public-read"), + PUBLIC_READ_WRITE("public-read-write"), + AUTHENTICATED_READ("authenticated-read"), + AWS_EXEC_READ("aws-exec-read"), + BUCKET_OWNER_READ("bucket-owner-read"), + BUCKET_OWNER_FULL_CONTROL("bucket-owner-full-control"); + + private final String value; + + private CannedAcl(String value) { + this.value = value; + } + + public String toString() { + return this.value; + } + + /** Returns CannedAcl of given string. */ + @JsonCreator + public static CannedAcl fromString(String cannedAclString) { + for (CannedAcl cannedAcl : CannedAcl.values()) { + if (cannedAclString.equals(cannedAcl.value)) { + return cannedAcl; + } + } + + throw new IllegalArgumentException("Unknown canned ACL '" + cannedAclString + "'"); + } + + /** XML converter class. */ + public static class CannedAclConverter implements Converter { + @Override + public CannedAcl read(InputNode node) throws Exception { + return CannedAcl.fromString(node.getValue()); + } + + @Override + public void write(OutputNode node, CannedAcl cannedAcl) throws Exception { + node.setValue(cannedAcl.toString()); + } + } + } + + @Root(name = "Encryption") + @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") + public static class Encryption { + @Element(name = "EncryptionType") + private SseAlgorithm encryptionType; + + @Element(name = "KMSContext", required = false) + private String kmsContext; + + @Element(name = "KMSKeyId", required = false) + private String kmsKeyId; + + public Encryption( + @Nonnull SseAlgorithm encryptionType, + @Nullable String kmsContext, + @Nullable String kmsKeyId) { + this.encryptionType = + Objects.requireNonNull(encryptionType, "Encryption type must not be null"); + this.kmsContext = kmsContext; + this.kmsKeyId = kmsKeyId; + } + } + + @Root(name = "UserMetadata", strict = false) + @Namespace(reference = "http://s3.amazonaws.com/doc/2006-03-01/") + @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") + public static class UserMetadata { + @ElementMap( + attribute = false, + entry = "MetadataEntry", + inline = true, + key = "Name", + value = "Value", + required = false) + Map metadataEntries; + + private UserMetadata(@Nonnull Map metadataEntries) { + Objects.requireNonNull(metadataEntries, "Metadata entries must not be null"); + if (metadataEntries.size() == 0) { + throw new IllegalArgumentException("Metadata entries must not be empty"); + } + this.metadataEntries = Utils.unmodifiableMap(metadataEntries); + } + } } diff --git a/api/src/main/java/io/minio/messages/Retention.java b/api/src/main/java/io/minio/messages/Retention.java index ade0cd21a..1662f2b00 100644 --- a/api/src/main/java/io/minio/messages/Retention.java +++ b/api/src/main/java/io/minio/messages/Retention.java @@ -16,6 +16,8 @@ package io.minio.messages; +import io.minio.Time; +import io.minio.Utils; import java.time.ZonedDateTime; import org.simpleframework.xml.Element; import org.simpleframework.xml.Namespace; @@ -35,7 +37,7 @@ public class Retention { private RetentionMode mode; @Element(name = "RetainUntilDate", required = false) - private ResponseDate retainUntilDate; + private Time.S3Time retainUntilDate; public Retention() {} @@ -50,7 +52,7 @@ public Retention(RetentionMode mode, ZonedDateTime retainUntilDate) { } this.mode = mode; - this.retainUntilDate = new ResponseDate(retainUntilDate); + this.retainUntilDate = new Time.S3Time(retainUntilDate); } /** Returns mode. */ @@ -60,10 +62,13 @@ public RetentionMode mode() { /** Returns retain until date. */ public ZonedDateTime retainUntilDate() { - if (retainUntilDate != null) { - return retainUntilDate.zonedDateTime(); - } + return retainUntilDate == null ? null : retainUntilDate.toZonedDateTime(); + } - return null; + @Override + public String toString() { + return String.format( + "Retention{mode=%s, retainUntilDate=%s}", + Utils.stringify(mode), Utils.stringify(retainUntilDate)); } } diff --git a/api/src/main/java/io/minio/messages/RetentionDuration.java b/api/src/main/java/io/minio/messages/RetentionDuration.java deleted file mode 100644 index 7550dbbd2..000000000 --- a/api/src/main/java/io/minio/messages/RetentionDuration.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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.messages; - -/** Retention duration of {@link ObjectLockConfiguration} */ -public interface RetentionDuration { - public RetentionDurationUnit unit(); - - public int duration(); -} diff --git a/api/src/main/java/io/minio/messages/RetentionDurationDays.java b/api/src/main/java/io/minio/messages/RetentionDurationDays.java deleted file mode 100644 index ad0eaa5ad..000000000 --- a/api/src/main/java/io/minio/messages/RetentionDurationDays.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.messages; - -import org.simpleframework.xml.Root; -import org.simpleframework.xml.Text; - -/** Days type retention duration of {@link ObjectLockConfiguration} */ -@Root(name = "Days") -public class RetentionDurationDays implements RetentionDuration { - @Text(required = false) - private Integer days; - - public RetentionDurationDays() {} - - public RetentionDurationDays(int days) { - this.days = Integer.valueOf(days); - } - - public RetentionDurationUnit unit() { - return RetentionDurationUnit.DAYS; - } - - public int duration() { - return days; - } - - /** Returns RetentionDurationDays as string. */ - @Override - public String toString() { - if (days == null) { - return ""; - } - return days.toString() + ((days == 1) ? " day" : " days"); - } -} diff --git a/api/src/main/java/io/minio/messages/RetentionDurationUnit.java b/api/src/main/java/io/minio/messages/RetentionDurationUnit.java deleted file mode 100644 index 22823805d..000000000 --- a/api/src/main/java/io/minio/messages/RetentionDurationUnit.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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.messages; - -/** Duration unit of default retention configuration. */ -public enum RetentionDurationUnit { - DAYS, - YEARS; -} diff --git a/api/src/main/java/io/minio/messages/RetentionDurationYears.java b/api/src/main/java/io/minio/messages/RetentionDurationYears.java deleted file mode 100644 index 50b75e663..000000000 --- a/api/src/main/java/io/minio/messages/RetentionDurationYears.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.messages; - -import org.simpleframework.xml.Root; -import org.simpleframework.xml.Text; - -/** Years type retention duration of {@link ObjectLockConfiguration} */ -@Root(name = "Years") -public class RetentionDurationYears implements RetentionDuration { - @Text(required = false) - private Integer years; - - public RetentionDurationYears() {} - - public RetentionDurationYears(int years) { - this.years = Integer.valueOf(years); - } - - public RetentionDurationUnit unit() { - return RetentionDurationUnit.YEARS; - } - - public int duration() { - return years; - } - - /** Returns RetentionDurationYears as string. */ - @Override - public String toString() { - if (years == null) { - return ""; - } - return years.toString() + ((years == 1) ? " year" : " years"); - } -} diff --git a/api/src/main/java/io/minio/messages/Rule.java b/api/src/main/java/io/minio/messages/Rule.java deleted file mode 100644 index 65cf0c829..000000000 --- a/api/src/main/java/io/minio/messages/Rule.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2019 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 org.simpleframework.xml.Element; -import org.simpleframework.xml.ElementUnion; -import org.simpleframework.xml.Path; -import org.simpleframework.xml.Root; - -/** Helper class to denote Rule information for {@link ObjectLockConfiguration}. */ -@Root(name = "Rule", strict = false) -public class Rule { - @Path(value = "DefaultRetention") - @Element(name = "Mode", required = false) - private RetentionMode mode; - - @Path(value = "DefaultRetention") - @ElementUnion({ - @Element(name = "Days", type = RetentionDurationDays.class, required = false), - @Element(name = "Years", type = RetentionDurationYears.class, required = false) - }) - private RetentionDuration duration; - - public Rule( - @Element(name = "Mode", required = false) RetentionMode mode, - @ElementUnion({ - @Element(name = "Days", type = RetentionDurationDays.class, required = false), - @Element(name = "Years", type = RetentionDurationYears.class, required = false) - }) - RetentionDuration duration) { - if (mode != null && duration != null) { - this.mode = mode; - this.duration = duration; - } else if (mode != null || duration != null) { - if (mode == null) { - throw new IllegalArgumentException("mode is null"); - } - throw new IllegalArgumentException("duration is null"); - } - } - - public RetentionMode mode() { - return mode; - } - - public RetentionDuration duration() { - return duration; - } -} diff --git a/api/src/main/java/io/minio/messages/RuleFilter.java b/api/src/main/java/io/minio/messages/RuleFilter.java deleted file mode 100644 index c5da2464a..000000000 --- a/api/src/main/java/io/minio/messages/RuleFilter.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * 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.messages; - -import java.util.Objects; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; -import org.simpleframework.xml.convert.Convert; - -/** - * Helper class to denote filter information for {@link ReplicationRule} and {@link LifecycleRule}. - */ -@Root(name = "Filter") -public class RuleFilter { - @Element(name = "And", required = false) - private AndOperator andOperator; - - @Element(name = "Prefix", required = false) - @Convert(PrefixConverter.class) - private String prefix; - - @Element(name = "Tag", required = false) - private Tag tag; - - @Element(name = "ObjectSizeLessThan", required = false) - private Long objectSizeLessThan; - - @Element(name = "ObjectSizeGreaterThan", required = false) - private Long objectSizeGreaterThan; - - public RuleFilter( - @Nullable @Element(name = "And", required = false) AndOperator andOperator, - @Nullable @Element(name = "Prefix", required = false) String prefix, - @Nullable @Element(name = "Tag", required = false) Tag tag) { - if (andOperator != null ^ prefix != null ^ tag != null) { - this.andOperator = andOperator; - this.prefix = prefix; - this.tag = tag; - } else { - throw new IllegalArgumentException("Only one of And, Prefix or Tag must be set"); - } - } - - public RuleFilter( - @Nullable @Element(name = "And", required = false) AndOperator andOperator, - @Nullable @Element(name = "Prefix", required = false) String prefix, - @Nullable @Element(name = "Tag", required = false) Tag tag, - @Nullable @Element(name = "ObjectSizeLessThan", required = false) Long objectSizeLessThan, - @Nullable @Element(name = "ObjectSizeGreaterThan", required = false) - Long objectSizeGreaterThan) { - this(andOperator, prefix, tag); - this.objectSizeLessThan = objectSizeLessThan; - this.objectSizeGreaterThan = objectSizeGreaterThan; - } - - public RuleFilter(@Nonnull AndOperator andOperator) { - this.andOperator = Objects.requireNonNull(andOperator, "And operator must not be null"); - } - - public RuleFilter(@Nonnull String prefix) { - this.prefix = Objects.requireNonNull(prefix, "Prefix must not be null"); - } - - public RuleFilter(@Nonnull Tag tag) { - this.tag = Objects.requireNonNull(tag, "Tag must not be null"); - } - - public AndOperator andOperator() { - return this.andOperator; - } - - public String prefix() { - return this.prefix; - } - - public Tag tag() { - return this.tag; - } - - public Long objectSizeLessThan() { - return this.objectSizeLessThan; - } - - public Long objectSizeGreaterThan() { - return this.objectSizeGreaterThan; - } -} diff --git a/api/src/main/java/io/minio/messages/S3OutputLocation.java b/api/src/main/java/io/minio/messages/S3OutputLocation.java deleted file mode 100644 index 17c0237c7..000000000 --- a/api/src/main/java/io/minio/messages/S3OutputLocation.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2021 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 java.util.Objects; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** Helper class to denote S3 output location information of {@link OutputLocation}. */ -@Root(name = "S3OutputLocation") -@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") -public class S3OutputLocation { - @Element(name = "AccessControlList", required = false) - private AccessControlList accessControlList; - - @Element(name = "BucketName") - private String bucketName; - - @Element(name = "CannedACL", required = false) - private CannedAcl cannedAcl; - - @Element(name = "Encryption", required = false) - private Encryption encryption; - - @Element(name = "Prefix") - private String prefix; - - @Element(name = "StorageClass", required = false) - private String storageClass; - - @Element(name = "Tagging", required = false) - private Tags tagging; - - @Element(name = "UserMetadata", required = false) - private UserMetadata userMetadata; - - public S3OutputLocation( - @Nonnull String bucketName, - @Nonnull String prefix, - @Nullable AccessControlList accessControlList, - @Nullable CannedAcl cannedAcl, - @Nullable Encryption encryption, - @Nullable String storageClass, - @Nullable Tags tagging, - @Nullable UserMetadata userMetadata) { - this.bucketName = Objects.requireNonNull(bucketName, "Bucket name must not be null"); - this.prefix = Objects.requireNonNull(prefix, "Prefix must not be null"); - this.accessControlList = accessControlList; - this.cannedAcl = cannedAcl; - this.encryption = encryption; - this.storageClass = storageClass; - this.tagging = tagging; - this.userMetadata = userMetadata; - } -} diff --git a/api/src/main/java/io/minio/messages/ScanRange.java b/api/src/main/java/io/minio/messages/ScanRange.java deleted file mode 100644 index 8e2c8b7dd..000000000 --- a/api/src/main/java/io/minio/messages/ScanRange.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2019 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 org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** - * Helper class to denote scan range in select object content request XML for {@link - * SelectObjectContentRequest}. - */ -@Root(name = "ScanRange") -@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") -public class ScanRange { - @Element(name = "Start", required = false) - private Long start; - - @Element(name = "End", required = false) - private Long end; - - /** Constructs new ScanRange object for given start and end. */ - public ScanRange(Long start, Long end) { - this.start = start; - this.end = end; - } -} diff --git a/api/src/main/java/io/minio/messages/SelectObjectContentRequest.java b/api/src/main/java/io/minio/messages/SelectObjectContentRequest.java index 59641837b..8904f0d2d 100644 --- a/api/src/main/java/io/minio/messages/SelectObjectContentRequest.java +++ b/api/src/main/java/io/minio/messages/SelectObjectContentRequest.java @@ -30,14 +30,13 @@ @Root(name = "SelectObjectContentRequest") @Namespace(reference = "http://s3.amazonaws.com/doc/2006-03-01/") @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") -public class SelectObjectContentRequest extends SelectObjectContentRequestBase { +public class SelectObjectContentRequest extends BaseSelectParameters { @Element(name = "RequestProgress", required = false) private RequestProgress requestProgress; @Element(name = "ScanRange", required = false) private ScanRange scanRange; - /** Constructs new SelectObjectContentRequest object for given parameters. */ public SelectObjectContentRequest( @Nonnull String expression, boolean requestProgress, @@ -53,4 +52,28 @@ public SelectObjectContentRequest( this.scanRange = new ScanRange(scanStartRange, scanEndRange); } } + + @Root(name = "RequestProgress", strict = false) + @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") + public static class RequestProgress { + @Element(name = "Enabled") + private boolean enabled = true; + + public RequestProgress() {} + } + + @Root(name = "ScanRange") + @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") + public static class ScanRange { + @Element(name = "Start", required = false) + private Long start; + + @Element(name = "End", required = false) + private Long end; + + public ScanRange(Long start, Long end) { + this.start = start; + this.end = end; + } + } } diff --git a/api/src/main/java/io/minio/messages/SelectParameters.java b/api/src/main/java/io/minio/messages/SelectParameters.java deleted file mode 100644 index cf8326ab9..000000000 --- a/api/src/main/java/io/minio/messages/SelectParameters.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2021 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 javax.annotation.Nonnull; -import org.simpleframework.xml.Root; - -/** - * Helper class to denote the parameters for Select job types information of {@link RestoreRequest}. - */ -@Root(name = "SelectParameters") -@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") -public class SelectParameters extends SelectObjectContentRequestBase { - public SelectParameters( - @Nonnull String expression, @Nonnull InputSerialization is, @Nonnull OutputSerialization os) { - super(expression, is, os); - } -} diff --git a/api/src/main/java/io/minio/messages/Source.java b/api/src/main/java/io/minio/messages/Source.java deleted file mode 100644 index fe5896e4c..000000000 --- a/api/src/main/java/io/minio/messages/Source.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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.messages; - -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * Helper class to denote client information causes this event. This is MinIO extension to Event - * Message Structure - */ -@edu.umd.cs.findbugs.annotations.SuppressFBWarnings( - value = "UwF", - justification = "Everything in this class is initialized by JSON unmarshalling.") -public class Source { - @JsonProperty private String host; - @JsonProperty private String port; - @JsonProperty private String userAgent; - - public String host() { - return host; - } - - public String port() { - return port; - } - - public String userAgent() { - return userAgent; - } -} diff --git a/api/src/main/java/io/minio/messages/SourceSelectionCriteria.java b/api/src/main/java/io/minio/messages/SourceSelectionCriteria.java deleted file mode 100644 index 542fc9617..000000000 --- a/api/src/main/java/io/minio/messages/SourceSelectionCriteria.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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.messages; - -import javax.annotation.Nullable; -import org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** Helper class to denote source selection criteria information for {@link ReplicationRule}. */ -@Root(name = "SourceSelectionCriteria") -public class SourceSelectionCriteria { - @Element(name = "ReplicaModifications", required = false) - private ReplicaModifications replicaModifications; - - @Element(name = "SseKmsEncryptedObjects", required = false) - private SseKmsEncryptedObjects sseKmsEncryptedObjects; - - public SourceSelectionCriteria( - @Nullable @Element(name = "SseKmsEncryptedObjects", required = false) - SseKmsEncryptedObjects sseKmsEncryptedObjects, - @Nullable @Element(name = "ReplicaModifications", required = false) - ReplicaModifications replicaModifications) { - this.sseKmsEncryptedObjects = sseKmsEncryptedObjects; - this.replicaModifications = replicaModifications; - } - - public SourceSelectionCriteria(@Nullable SseKmsEncryptedObjects sseKmsEncryptedObjects) { - this(sseKmsEncryptedObjects, null); - } - - public ReplicaModifications replicaModifications() { - return this.replicaModifications; - } - - public SseKmsEncryptedObjects sseKmsEncryptedObjects() { - return this.sseKmsEncryptedObjects; - } -} diff --git a/api/src/main/java/io/minio/messages/SseConfiguration.java b/api/src/main/java/io/minio/messages/SseConfiguration.java index 1107be67d..a6096106a 100644 --- a/api/src/main/java/io/minio/messages/SseConfiguration.java +++ b/api/src/main/java/io/minio/messages/SseConfiguration.java @@ -16,9 +16,13 @@ package io.minio.messages; +import io.minio.Utils; +import java.util.Objects; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.simpleframework.xml.Element; import org.simpleframework.xml.Namespace; +import org.simpleframework.xml.Path; import org.simpleframework.xml.Root; /** @@ -28,27 +32,67 @@ * href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketEncryption.html">GetBucketEncryption * API. */ -@Root(name = "ServerSideEncryptionConfiguration") +@Root(name = "ServerSideEncryptionConfiguration", strict = false) @Namespace(reference = "http://s3.amazonaws.com/doc/2006-03-01/") @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") public class SseConfiguration { @Element(name = "Rule", required = false) - private SseConfigurationRule rule; + private Rule rule; - public SseConfiguration( - @Nullable @Element(name = "Rule", required = false) SseConfigurationRule rule) { + public SseConfiguration(@Nullable @Element(name = "Rule", required = false) Rule rule) { this.rule = rule; } public static SseConfiguration newConfigWithSseS3Rule() { - return new SseConfiguration(new SseConfigurationRule(SseAlgorithm.AES256, null)); + return new SseConfiguration(new Rule(SseAlgorithm.AES256, null)); } public static SseConfiguration newConfigWithSseKmsRule(@Nullable String kmsMasterKeyId) { - return new SseConfiguration(new SseConfigurationRule(SseAlgorithm.AWS_KMS, kmsMasterKeyId)); + return new SseConfiguration(new Rule(SseAlgorithm.AWS_KMS, kmsMasterKeyId)); } - public SseConfigurationRule rule() { + public Rule rule() { return this.rule; } + + @Override + public String toString() { + return String.format("SseConfiguration{rule=%s}", Utils.stringify(rule)); + } + + @Root(name = "Rule", strict = false) + @Namespace(reference = "http://s3.amazonaws.com/doc/2006-03-01/") + @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") + public static class Rule { + @Path(value = "ApplyServerSideEncryptionByDefault") + @Element(name = "KMSMasterKeyID", required = false) + private String kmsMasterKeyId; + + @Path(value = "ApplyServerSideEncryptionByDefault") + @Element(name = "SSEAlgorithm") + private SseAlgorithm sseAlgorithm; + + /** Constructs new server-side encryption configuration rule. */ + public Rule( + @Nonnull @Element(name = "SSEAlgorithm") SseAlgorithm sseAlgorithm, + @Nullable @Element(name = "KMSMasterKeyID", required = false) String kmsMasterKeyId) { + this.sseAlgorithm = Objects.requireNonNull(sseAlgorithm, "SSE Algorithm must be provided"); + this.kmsMasterKeyId = kmsMasterKeyId; + } + + public String kmsMasterKeyId() { + return this.kmsMasterKeyId; + } + + public SseAlgorithm sseAlgorithm() { + return this.sseAlgorithm; + } + + @Override + public String toString() { + return String.format( + "Rule{sseAlgorithm=%s, kmsMasterKeyId=%s}", + Utils.stringify(sseAlgorithm), Utils.stringify(kmsMasterKeyId)); + } + } } diff --git a/api/src/main/java/io/minio/messages/SseConfigurationRule.java b/api/src/main/java/io/minio/messages/SseConfigurationRule.java deleted file mode 100644 index ff997a39c..000000000 --- a/api/src/main/java/io/minio/messages/SseConfigurationRule.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.messages; - -import java.util.Objects; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.simpleframework.xml.Element; -import org.simpleframework.xml.Namespace; -import org.simpleframework.xml.Path; -import org.simpleframework.xml.Root; - -/** Helper class to denote Rule information for {@link SseConfiguration}. */ -@Root(name = "Rule") -@Namespace(reference = "http://s3.amazonaws.com/doc/2006-03-01/") -@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") -public class SseConfigurationRule { - @Path(value = "ApplyServerSideEncryptionByDefault") - @Element(name = "KMSMasterKeyID", required = false) - private String kmsMasterKeyId; - - @Path(value = "ApplyServerSideEncryptionByDefault") - @Element(name = "SSEAlgorithm") - private SseAlgorithm sseAlgorithm; - - /** Constructs new server-side encryption configuration rule. */ - public SseConfigurationRule( - @Nonnull @Element(name = "SSEAlgorithm") SseAlgorithm sseAlgorithm, - @Nullable @Element(name = "KMSMasterKeyID", required = false) String kmsMasterKeyId) { - this.sseAlgorithm = Objects.requireNonNull(sseAlgorithm, "SSE Algorithm must be provided"); - this.kmsMasterKeyId = kmsMasterKeyId; - } - - public String kmsMasterKeyId() { - return this.kmsMasterKeyId; - } - - public SseAlgorithm sseAlgorithm() { - return this.sseAlgorithm; - } -} diff --git a/api/src/main/java/io/minio/messages/SseKmsEncryptedObjects.java b/api/src/main/java/io/minio/messages/SseKmsEncryptedObjects.java deleted file mode 100644 index 1cdf045eb..000000000 --- a/api/src/main/java/io/minio/messages/SseKmsEncryptedObjects.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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.messages; - -import java.util.Objects; -import javax.annotation.Nonnull; -import org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** - * Helper class to denote SSE KMS encrypted objects information for {@link SourceSelectionCriteria}. - */ -@Root(name = "SseKmsEncryptedObjects") -public class SseKmsEncryptedObjects { - @Element(name = "Status") - private Status status; - - public SseKmsEncryptedObjects(@Nonnull @Element(name = "Status") Status status) { - this.status = Objects.requireNonNull(status, "Status must not be null"); - } - - public Status status() { - return this.status; - } -} diff --git a/api/src/main/java/io/minio/messages/Stats.java b/api/src/main/java/io/minio/messages/Stats.java index da8471b83..664a0abb4 100644 --- a/api/src/main/java/io/minio/messages/Stats.java +++ b/api/src/main/java/io/minio/messages/Stats.java @@ -24,30 +24,48 @@ @Root(name = "Stats", strict = false) @Namespace(reference = "http://s3.amazonaws.com/doc/2006-03-01/") public class Stats { - @Element(name = "BytesScanned") - private long bytesScanned = -1; + @Element(name = "BytesScanned", required = false) + private Long bytesScanned; - @Element(name = "BytesProcessed") - private long bytesProcessed = -1; + @Element(name = "BytesProcessed", required = false) + private Long bytesProcessed; - @Element(name = "BytesReturned") - private long bytesReturned = -1; + @Element(name = "BytesReturned", required = false) + private Long bytesReturned; /** Constructs a new Stats object. */ - public Stats() {} + public Stats( + @Element(name = "BytesScanned", required = false) Long bytesScanned, + @Element(name = "BytesProcessed", required = false) Long bytesProcessed, + @Element(name = "BytesReturned", required = false) Long bytesReturned) { + this.bytesScanned = bytesScanned; + this.bytesProcessed = bytesProcessed; + this.bytesReturned = bytesReturned; + } /** Returns bytes scanned. */ - public long bytesScanned() { - return this.bytesScanned; + public Long bytesScanned() { + return bytesScanned; } /** Returns bytes processed. */ - public long bytesProcessed() { - return this.bytesProcessed; + public Long bytesProcessed() { + return bytesProcessed; } /** Returns bytes returned. */ - public long bytesReturned() { - return this.bytesReturned; + public Long bytesReturned() { + return bytesReturned; + } + + protected String stringify() { + return String.format( + "bytesScanned=%s, bytesProcessed=%s, bytesReturned=%s", + bytesScanned, bytesProcessed, bytesReturned); + } + + @Override + public String toString() { + return String.format("Stats{%s}", stringify()); } } diff --git a/api/src/main/java/io/minio/messages/Tag.java b/api/src/main/java/io/minio/messages/Tag.java deleted file mode 100644 index 841e38ae9..000000000 --- a/api/src/main/java/io/minio/messages/Tag.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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.messages; - -import java.util.Objects; -import javax.annotation.Nonnull; -import org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** Helper class to denote tag information for {@link RuleFilter}. */ -@Root(name = "Tag") -public class Tag { - @Element(name = "Key") - private String key; - - @Element(name = "Value") - private String value; - - public Tag( - @Nonnull @Element(name = "Key") String key, @Nonnull @Element(name = "Value") String value) { - Objects.requireNonNull(key, "Key must not be null"); - if (key.isEmpty()) { - throw new IllegalArgumentException("Key must not be empty"); - } - - this.key = key; - this.value = Objects.requireNonNull(value, "Value must not be null"); - } - - public String key() { - return this.key; - } - - public String value() { - return this.value; - } -} diff --git a/api/src/main/java/io/minio/messages/Tags.java b/api/src/main/java/io/minio/messages/Tags.java index 1d191c109..a38fe4f43 100644 --- a/api/src/main/java/io/minio/messages/Tags.java +++ b/api/src/main/java/io/minio/messages/Tags.java @@ -24,11 +24,11 @@ import org.simpleframework.xml.Root; /** - * Object representation of request XML of PutBucketTagging - * API and , PutObjectTagging - * API response XML of , GetBucketTagging * API and GetObjectTagging @@ -103,4 +103,9 @@ public static Tags newObjectTags(Map tags) throws IllegalArgumen public Map get() { return Utils.unmodifiableMap(tags); } + + @Override + public String toString() { + return String.format("Tags{%s}", Utils.stringify(tags)); + } } diff --git a/api/src/main/java/io/minio/messages/Tier.java b/api/src/main/java/io/minio/messages/Tier.java deleted file mode 100644 index f22df7dee..000000000 --- a/api/src/main/java/io/minio/messages/Tier.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2021 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.fasterxml.jackson.annotation.JsonCreator; -import org.simpleframework.xml.Root; -import org.simpleframework.xml.convert.Convert; -import org.simpleframework.xml.convert.Converter; -import org.simpleframework.xml.stream.InputNode; -import org.simpleframework.xml.stream.OutputNode; - -/** Tier representing retrieval tier value. */ -@Root(name = "Tier") -@Convert(Tier.TierConverter.class) -public enum Tier { - STANDARD("Standard"), - BULK("Bulk"), - EXPEDITED("Expedited"); - - private final String value; - - private Tier(String value) { - this.value = value; - } - - public String toString() { - return this.value; - } - - /** Returns Tier of given string. */ - @JsonCreator - public static Tier fromString(String tierString) { - for (Tier tier : Tier.values()) { - if (tierString.equals(tier.value)) { - return tier; - } - } - - throw new IllegalArgumentException("Unknown tier '" + tierString + "'"); - } - - /** XML converter class. */ - public static class TierConverter implements Converter { - @Override - public Tier read(InputNode node) throws Exception { - return Tier.fromString(node.getValue()); - } - - @Override - public void write(OutputNode node, Tier tier) throws Exception { - node.setValue(tier.toString()); - } - } -} diff --git a/api/src/main/java/io/minio/messages/TopicConfiguration.java b/api/src/main/java/io/minio/messages/TopicConfiguration.java deleted file mode 100644 index 6c9a49df0..000000000 --- a/api/src/main/java/io/minio/messages/TopicConfiguration.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2017 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 org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** Helper class to denote Topic configuration of {@link NotificationConfiguration}. */ -@Root(name = "TopicConfiguration", strict = false) -public class TopicConfiguration extends NotificationCommonConfiguration { - @Element(name = "Topic") - private String topic; - - public TopicConfiguration() { - super(); - } - - /** Returns topic. */ - public String topic() { - return topic; - } - - /** Sets topic. */ - public void setTopic(String topic) { - this.topic = topic; - } -} diff --git a/api/src/main/java/io/minio/messages/Transition.java b/api/src/main/java/io/minio/messages/Transition.java deleted file mode 100644 index 483364e45..000000000 --- a/api/src/main/java/io/minio/messages/Transition.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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.messages; - -import java.time.ZonedDateTime; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** Helper class to denote transition information for {@link LifecycleRule}. */ -@Root(name = "Transition") -public class Transition extends DateDays { - @Element(name = "StorageClass") - private String storageClass; - - public Transition( - @Nullable @Element(name = "Date", required = false) ResponseDate date, - @Nullable @Element(name = "Days", required = false) Integer days, - @Nonnull @Element(name = "StorageClass", required = false) String storageClass) { - if (date != null ^ days != null) { - this.date = date; - this.days = days; - } else { - throw new IllegalArgumentException("Only one of date or days must be set"); - } - if (storageClass == null || storageClass.isEmpty()) { - throw new IllegalArgumentException("StorageClass must be provided"); - } - this.storageClass = storageClass; - } - - public Transition(ZonedDateTime date, Integer days, String storageClass) { - this(date == null ? null : new ResponseDate(date), days, storageClass); - } - - public String storageClass() { - return storageClass; - } -} diff --git a/api/src/main/java/io/minio/messages/Upload.java b/api/src/main/java/io/minio/messages/Upload.java deleted file mode 100644 index bf9b8a1ed..000000000 --- a/api/src/main/java/io/minio/messages/Upload.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2015 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 org.simpleframework.xml.Element; -import org.simpleframework.xml.Namespace; -import org.simpleframework.xml.Root; - -/** - * Helper class to denote Upload information of a multipart upload and used in {@link - * ListMultipartUploadsResult}. - */ -@Root(name = "Upload", strict = false) -@Namespace(reference = "http://s3.amazonaws.com/doc/2006-03-01/") -public class Upload { - @Element(name = "Key") - private String objectName; - - @Element(name = "UploadId") - private String uploadId; - - @Element(name = "Initiator") - private Initiator initiator; - - @Element(name = "Owner") - private Owner owner; - - @Element(name = "StorageClass") - private String storageClass; - - @Element(name = "Initiated") - private ResponseDate initiated; - - @Element(name = "ChecksumAlgorithm", required = false) - private String checksumAlgorithm; - - @Element(name = "ChecksumType", required = false) - private String checksumType; - - private long aggregatedPartSize; - private String encodingType = null; - - public Upload() {} - - /** Returns object name. */ - public String objectName() { - return Utils.urlDecode(objectName, encodingType); - } - - /** Returns upload ID. */ - public String uploadId() { - return uploadId; - } - - /** Returns initator information. */ - public Initiator initiator() { - return initiator; - } - - /** Returns owner information. */ - public Owner owner() { - return owner; - } - - /** Returns storage class. */ - public String storageClass() { - return storageClass; - } - - /** Returns initated time. */ - public ZonedDateTime initiated() { - return initiated.zonedDateTime(); - } - - /** Returns aggregated part size. */ - public long aggregatedPartSize() { - return aggregatedPartSize; - } - - /** Sets given aggregated part size. */ - public void setAggregatedPartSize(long size) { - this.aggregatedPartSize = size; - } - - public void setEncodingType(String encodingType) { - this.encodingType = encodingType; - } - - public String checksumAlgorithm() { - return checksumAlgorithm; - } - - public String checksumType() { - return checksumType; - } -} diff --git a/api/src/main/java/io/minio/messages/UserMetadata.java b/api/src/main/java/io/minio/messages/UserMetadata.java deleted file mode 100644 index 727e2b3fc..000000000 --- a/api/src/main/java/io/minio/messages/UserMetadata.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2021 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.Map; -import java.util.Objects; -import javax.annotation.Nonnull; -import org.simpleframework.xml.ElementMap; -import org.simpleframework.xml.Namespace; -import org.simpleframework.xml.Root; - -/** Helper class to denote user metadata information of {@link S3OutputLocation}. */ -@Root(name = "UserMetadata", strict = false) -@Namespace(reference = "http://s3.amazonaws.com/doc/2006-03-01/") -@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "URF_UNREAD_FIELD") -public class UserMetadata { - @ElementMap( - attribute = false, - entry = "MetadataEntry", - inline = true, - key = "Name", - value = "Value", - required = false) - Map metadataEntries; - - private UserMetadata(@Nonnull Map metadataEntries) { - Objects.requireNonNull(metadataEntries, "Metadata entries must not be null"); - if (metadataEntries.size() == 0) { - throw new IllegalArgumentException("Metadata entries must not be empty"); - } - this.metadataEntries = Utils.unmodifiableMap(metadataEntries); - } -} diff --git a/api/src/main/java/io/minio/messages/Version.java b/api/src/main/java/io/minio/messages/Version.java deleted file mode 100644 index 65eb57d97..000000000 --- a/api/src/main/java/io/minio/messages/Version.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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.messages; - -import org.simpleframework.xml.Root; - -/** Helper class to denote object and it's version information in {@link ListVersionsResult}. */ -@Root(name = "Version", strict = false) -public class Version extends Item { - public Version() { - super(); - } - - public Version(String prefix) { - super(prefix); - } -} diff --git a/api/src/main/java/io/minio/messages/VersioningConfiguration.java b/api/src/main/java/io/minio/messages/VersioningConfiguration.java index d67536586..272e2b2e8 100644 --- a/api/src/main/java/io/minio/messages/VersioningConfiguration.java +++ b/api/src/main/java/io/minio/messages/VersioningConfiguration.java @@ -16,6 +16,7 @@ package io.minio.messages; +import io.minio.Utils; import java.util.Objects; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -63,6 +64,13 @@ public Boolean isMfaDeleteEnabled() { return flag; } + @Override + public String toString() { + return String.format( + "VersioningConfiguration{status=%s, mfaDelete=%s}", + Utils.stringify(status), Utils.stringify(mfaDelete)); + } + public static enum Status { OFF(""), ENABLED("Enabled"), diff --git a/api/src/main/java/io/minio/org/apache/commons/validator/routines/InetAddressValidator.java b/api/src/main/java/io/minio/org/apache/commons/validator/routines/InetAddressValidator.java deleted file mode 100644 index 5101f593b..000000000 --- a/api/src/main/java/io/minio/org/apache/commons/validator/routines/InetAddressValidator.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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.org.apache.commons.validator.routines; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * InetAddress validation and conversion routines (java.net.InetAddress). - * - *

- * - *

- * - *

This class provides methods to validate a candidate IP address. - * - *

- * - *

This class is a Singleton; you can retrieve the instance via the {@link #getInstance()} - * method. - * - * @version $Revision$ - * @since Validator 1.4 - */ -public class InetAddressValidator { - - private static final int IPV4_MAX_OCTET_VALUE = 255; - - private static final int MAX_UNSIGNED_SHORT = 0xffff; - - private static final int BASE_16 = 16; - - private static final long serialVersionUID = -919201640201914789L; - - private static final String IPV4_REGEX = "^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$"; - - // Max number of hex groups (separated by :) in an IPV6 address - private static final int IPV6_MAX_HEX_GROUPS = 8; - - // Max hex digits in each IPv6 group - private static final int IPV6_MAX_HEX_DIGITS_PER_GROUP = 4; - - /** Singleton instance of this class. */ - private static final InetAddressValidator VALIDATOR = new InetAddressValidator(); - - /** IPv4 RegexValidator. */ - private final RegexValidator ipv4Validator = new RegexValidator(IPV4_REGEX); - - private InetAddressValidator() {} - - /** - * Returns the singleton instance of this validator. - * - * @return the singleton instance of this validator - */ - public static InetAddressValidator getInstance() { - return VALIDATOR; - } - - /** - * Checks if the specified string is a valid IP address. - * - * @param inetAddress the string to validate - * @return true if the string validates as an IP address - */ - public boolean isValid(String inetAddress) { - return isValidInet4Address(inetAddress) || isValidInet6Address(inetAddress); - } - - /** - * Validates an IPv4 address. Returns true if valid. - * - * @param inet4Address the IPv4 address to validate - * @return true if the argument contains a valid IPv4 address - */ - public boolean isValidInet4Address(String inet4Address) { - // verify that address conforms to generic IPv4 format - String[] groups = ipv4Validator.match(inet4Address); - - if (groups == null) { - return false; - } - - // verify that address subgroups are legal - for (String ipSegment : groups) { - if (ipSegment == null || ipSegment.length() == 0) { - return false; - } - - int iIpSegment = 0; - - try { - iIpSegment = Integer.parseInt(ipSegment); - } catch (NumberFormatException e) { - return false; - } - - if (iIpSegment > IPV4_MAX_OCTET_VALUE) { - return false; - } - - if (ipSegment.length() > 1 && ipSegment.startsWith("0")) { - return false; - } - } - - return true; - } - - /** - * Validates an IPv6 address. Returns true if valid. - * - * @param inet6Address the IPv6 address to validate - * @return true if the argument contains a valid IPv6 address - * @since 1.4.1 - */ - public boolean isValidInet6Address(String inet6Address) { - boolean containsCompressedZeroes = inet6Address.contains("::"); - if (containsCompressedZeroes && inet6Address.indexOf("::") != inet6Address.lastIndexOf("::")) { - return false; - } - if (inet6Address.startsWith(":") && !inet6Address.startsWith("::") - || inet6Address.endsWith(":") && !inet6Address.endsWith("::")) { - return false; - } - String[] octets = inet6Address.split(":"); - if (containsCompressedZeroes) { - List octetList = new ArrayList(Arrays.asList(octets)); - if (inet6Address.endsWith("::")) { - // String.split() drops ending empty segments - octetList.add(""); - } else if (inet6Address.startsWith("::") && !octetList.isEmpty()) { - octetList.remove(0); - } - octets = octetList.toArray(new String[octetList.size()]); - } - if (octets.length > IPV6_MAX_HEX_GROUPS) { - return false; - } - int validOctets = 0; - int emptyOctets = 0; - for (int index = 0; index < octets.length; index++) { - String octet = octets[index]; - if (octet.length() == 0) { - emptyOctets++; - if (emptyOctets > 1) { - return false; - } - } else { - emptyOctets = 0; - if (octet.contains(".")) { // contains is Java 1.5+ - if (!inet6Address.endsWith(octet)) { - return false; - } - if (index > octets.length - 1 || index > 6) { // CHECKSTYLE IGNORE MagicNumber - // IPV4 occupies last two octets - return false; - } - if (!isValidInet4Address(octet)) { - return false; - } - validOctets += 2; - continue; - } - if (octet.length() > IPV6_MAX_HEX_DIGITS_PER_GROUP) { - return false; - } - int octetInt = 0; - try { - octetInt = Integer.valueOf(octet, BASE_16).intValue(); - } catch (NumberFormatException e) { - return false; - } - if (octetInt < 0 || octetInt > MAX_UNSIGNED_SHORT) { - return false; - } - } - validOctets++; - } - if (validOctets < IPV6_MAX_HEX_GROUPS && !containsCompressedZeroes) { - return false; - } - return true; - } -} diff --git a/api/src/main/java/io/minio/org/apache/commons/validator/routines/RegexValidator.java b/api/src/main/java/io/minio/org/apache/commons/validator/routines/RegexValidator.java deleted file mode 100644 index 31745dae4..000000000 --- a/api/src/main/java/io/minio/org/apache/commons/validator/routines/RegexValidator.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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.org.apache.commons.validator.routines; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import java.io.Serializable; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Regular Expression validation (using JDK 1.4+ regex support). - * - *

Construct the validator either for a single regular expression or a set (array) of regular - * expressions. By default validation is case sensitive but constructors are provided to - * allow case in-sensitive validation. For example to create a validator which does case - * in-sensitive validation for a set of regular expressions: - * - *

- * 
- * String[] regexs = new String[] {...};
- * RegexValidator validator = new RegexValidator(regexs, false);
- * 
- * 
- * - *

- * - *

    - *
  • Validate true or false: - *
  • - *
      - *
    • boolean valid = validator.isValid(value); - *
    - *
  • Validate returning an aggregated String of the matched groups: - *
  • - *
      - *
    • String result = validator.validate(value); - *
    - *
  • Validate returning the matched groups: - *
  • - *
      - *
    • String[] result = validator.match(value); - *
    - *
- * - *

Note that patterns are matched against the entire input. - * - *

- * - *

Cached instances pre-compile and re-use {@link Pattern}(s) - which according to the {@link - * Pattern} API are safe to use in a multi-threaded environment. - * - * @version $Revision$ - * @since Validator 1.4 - */ -public class RegexValidator implements Serializable { - - private static final long serialVersionUID = -8832409930574867162L; - - private final Pattern[] patterns; - - /** - * Construct a case sensitive validator for a single regular expression. - * - * @param regex The regular expression this validator will validate against - */ - public RegexValidator(String regex) { - this(regex, true); - } - - /** - * Construct a validator for a single regular expression with the specified case sensitivity. - * - * @param regex The regular expression this validator will validate against - * @param caseSensitive when true matching is case sensitive, otherwise - * matching is case in-sensitive - */ - public RegexValidator(String regex, boolean caseSensitive) { - this(new String[] {regex}, caseSensitive); - } - - /** - * Construct a case sensitive validator that matches any one of the set of regular - * expressions. - * - * @param regexs The set of regular expressions this validator will validate against - */ - public RegexValidator(String[] regexs) { - this(regexs, true); - } - - /** - * Construct a validator that matches any one of the set of regular expressions with the specified - * case sensitivity. - * - * @param regexs The set of regular expressions this validator will validate against - * @param caseSensitive when true matching is case sensitive, otherwise - * matching is case in-sensitive - */ - public RegexValidator(String[] regexs, boolean caseSensitive) { - if (regexs == null || regexs.length == 0) { - throw new IllegalArgumentException("Regular expressions are missing"); - } - patterns = new Pattern[regexs.length]; - int flags = (caseSensitive ? 0 : Pattern.CASE_INSENSITIVE); - for (int i = 0; i < regexs.length; i++) { - if (regexs[i] == null || regexs[i].length() == 0) { - throw new IllegalArgumentException("Regular expression[" + i + "] is missing"); - } - patterns[i] = Pattern.compile(regexs[i], flags); - } - } - - /** - * Validate a value against the set of regular expressions. - * - * @param value The value to validate. - * @return true if the value is valid otherwise false. - */ - public boolean isValid(String value) { - if (value == null) { - return false; - } - for (int i = 0; i < patterns.length; i++) { - if (patterns[i].matcher(value).matches()) { - return true; - } - } - return false; - } - - /** - * Validate a value against the set of regular expressions returning the array of matched groups. - * - * @param value The value to validate. - * @return String array of the groups matched if valid or null if invalid - */ - @SuppressFBWarnings( - value = "PZLA", - justification = "Null is checked, not empty array. API is clear as well.") - public String[] match(String value) { - if (value == null) { - return null; - } - for (int i = 0; i < patterns.length; i++) { - Matcher matcher = patterns[i].matcher(value); - if (matcher.matches()) { - int count = matcher.groupCount(); - String[] groups = new String[count]; - for (int j = 0; j < count; j++) { - groups[j] = matcher.group(j + 1); - } - return groups; - } - } - return null; - } - - /** - * Validate a value against the set of regular expressions returning a String value of the - * aggregated groups. - * - * @param value The value to validate. - * @return Aggregated String value comprised of the groups matched if valid or null - * if invalid - */ - public String validate(String value) { - if (value == null) { - return null; - } - for (int i = 0; i < patterns.length; i++) { - Matcher matcher = patterns[i].matcher(value); - if (matcher.matches()) { - int count = matcher.groupCount(); - if (count == 1) { - return matcher.group(1); - } - StringBuilder buffer = new StringBuilder(); - for (int j = 0; j < count; j++) { - String component = matcher.group(j + 1); - if (component != null) { - buffer.append(component); - } - } - return buffer.toString(); - } - } - return null; - } - - /** - * Provide a String representation of this validator. - * - * @return A String representation of this validator - */ - @Override - public String toString() { - StringBuilder buffer = new StringBuilder(); - buffer.append("RegexValidator{"); - for (int i = 0; i < patterns.length; i++) { - if (i > 0) { - buffer.append(","); - } - buffer.append(patterns[i].pattern()); - } - buffer.append("}"); - return buffer.toString(); - } -} diff --git a/api/src/test/java/io/minio/MakeBucketArgsTest.java b/api/src/test/java/io/minio/MakeBucketArgsTest.java index a3cee0322..a47b57994 100644 --- a/api/src/test/java/io/minio/MakeBucketArgsTest.java +++ b/api/src/test/java/io/minio/MakeBucketArgsTest.java @@ -21,25 +21,25 @@ import org.junit.Test; public class MakeBucketArgsTest { - @Test(expected = IllegalArgumentException.class) + @Test(expected = NullPointerException.class) public void testEmptyBuild() { MakeBucketArgs.builder().build(); Assert.fail("exception should be thrown"); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = NullPointerException.class) public void testEmptyBucketBuild1() { MakeBucketArgs.builder().objectLock(false).build(); Assert.fail("exception should be thrown"); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = NullPointerException.class) public void testEmptyBucketBuild2() { MakeBucketArgs.builder().bucket(null).build(); Assert.fail("exception should be thrown"); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = NullPointerException.class) public void testEmptyBucketBuild3() { MakeBucketArgs.builder().bucket("mybucket").bucket(null).build(); Assert.fail("exception should be thrown"); diff --git a/api/src/test/java/io/minio/MinioClientTest.java b/api/src/test/java/io/minio/MinioClientTest.java index bc97d607b..eea90951a 100644 --- a/api/src/test/java/io/minio/MinioClientTest.java +++ b/api/src/test/java/io/minio/MinioClientTest.java @@ -19,7 +19,6 @@ import io.minio.errors.InvalidResponseException; import io.minio.errors.MinioException; -import io.minio.http.Method; import java.io.ByteArrayInputStream; import java.io.IOException; import java.security.InvalidKeyException; @@ -38,7 +37,7 @@ public class MinioClientTest { private static final String CONTENT_TYPE = "Content-Type"; private static final String CONTENT_LENGTH = "Content-Length"; - @Test(expected = IllegalArgumentException.class) + @Test(expected = NullPointerException.class) public void testEndpoint1() throws MinioException { MinioClient.builder().endpoint((String) null).build(); Assert.fail("exception should be thrown"); @@ -103,7 +102,7 @@ public void testAwsEndpoints() url = client.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() - .method(Method.GET) + .method(Http.Method.GET) .bucket("mybucket") .object("myobject") .build()); @@ -118,7 +117,7 @@ public void testAwsEndpoints() url = client.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() - .method(Method.GET) + .method(Http.Method.GET) .bucket("mybucket") .object("myobject") .build()); @@ -129,7 +128,7 @@ public void testAwsEndpoints() url = client.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() - .method(Method.GET) + .method(Http.Method.GET) .bucket("mybucket") .object("myobject") .build()); @@ -144,7 +143,7 @@ public void testAwsEndpoints() url = client.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() - .method(Method.GET) + .method(Http.Method.GET) .bucket("mybucket") .object("myobject") .build()); @@ -156,7 +155,7 @@ public void testAwsEndpoints() url = client.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() - .method(Method.GET) + .method(Http.Method.GET) .bucket("mybucket") .object("myobject") .build()); @@ -169,7 +168,7 @@ public void testAwsEndpoints() url = client.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() - .method(Method.GET) + .method(Http.Method.GET) .bucket("mybucket") .object("myobject") .build()); @@ -185,7 +184,7 @@ public void testAwsEndpoints() url = client.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() - .method(Method.GET) + .method(Http.Method.GET) .bucket("mybucket") .object("myobject") .build()); @@ -197,7 +196,7 @@ public void testAwsEndpoints() url = client.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() - .method(Method.GET) + .method(Http.Method.GET) .bucket("mybucket") .object("myobject") .build()); @@ -213,7 +212,7 @@ public void testAwsEndpoints() url = client.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() - .method(Method.GET) + .method(Http.Method.GET) .bucket("mybucket") .object("myobject") .build()); @@ -226,7 +225,7 @@ public void testAwsEndpoints() url = client.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() - .method(Method.GET) + .method(Http.Method.GET) .bucket("mybucket") .object("myobject") .build()); @@ -243,7 +242,7 @@ public void testAwsEndpoints() url = client.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() - .method(Method.GET) + .method(Http.Method.GET) .bucket("mybucket") .object("myobject") .build()); @@ -258,7 +257,7 @@ public void testAwsEndpoints() url = client.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() - .method(Method.GET) + .method(Http.Method.GET) .bucket("mybucket") .object("myobject") .build()); @@ -273,7 +272,7 @@ public void testAwsEndpoints() url = client.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() - .method(Method.GET) + .method(Http.Method.GET) .bucket("mybucket") .object("myobject") .build()); @@ -289,7 +288,7 @@ public void testAwsEndpoints() url = client.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() - .method(Method.GET) + .method(Http.Method.GET) .bucket("mybucket") .object("myobject") .build()); @@ -306,7 +305,7 @@ public void testAwsEndpoints() url = client.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() - .method(Method.GET) + .method(Http.Method.GET) .bucket("mybucket") .object("myobject") .build()); @@ -322,7 +321,7 @@ public void testAwsEndpoints() url = client.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() - .method(Method.GET) + .method(Http.Method.GET) .bucket("mybucket") .object("myobject") .build()); @@ -338,7 +337,7 @@ public void testAwsEndpoints() url = client.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() - .method(Method.GET) + .method(Http.Method.GET) .bucket("mybucket") .object("myobject") .build()); @@ -355,7 +354,7 @@ public void testAwsEndpoints() url = client.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() - .method(Method.GET) + .method(Http.Method.GET) .bucket("mybucket") .object("myobject") .build()); @@ -382,7 +381,7 @@ public void testCustomHttpClientClose() throws Exception { Assert.assertTrue(httpClient.dispatcher().executorService().isShutdown()); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = NullPointerException.class) public void testBucketName1() throws NoSuchAlgorithmException, IOException, InvalidKeyException, MinioException { StatObjectArgs.builder().bucket(null); @@ -432,7 +431,7 @@ public void testBucketName7() Assert.fail("exception should be thrown"); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = NullPointerException.class) public void testObjectName1() throws NoSuchAlgorithmException, IOException, InvalidKeyException, MinioException { StatObjectArgs.builder().object(null); @@ -470,7 +469,7 @@ public void testReadSse1() StatObjectArgs.builder() .bucket("mybucket") .object("myobject") - .ssec(new ServerSideEncryptionCustomerKey(keyGen.generateKey())) + .ssec(new ServerSideEncryption.CustomerKey(keyGen.generateKey())) .build()); Assert.fail("exception should be thrown"); } @@ -484,7 +483,7 @@ public void testWriteSse1() client.putObject( PutObjectArgs.builder().bucket("mybucket").object("myobject").stream( new ByteArrayInputStream(new byte[] {}), 0, -1) - .sse(new ServerSideEncryptionCustomerKey(keyGen.generateKey())) + .sse(new ServerSideEncryption.CustomerKey(keyGen.generateKey())) .build()); Assert.fail("exception should be thrown"); } @@ -498,7 +497,7 @@ public void testWriteSse2() client.putObject( PutObjectArgs.builder().bucket("mybucket").object("myobject").stream( new ByteArrayInputStream(new byte[] {}), 0, -1) - .sse(new ServerSideEncryptionKms("keyId", myContext)) + .sse(new ServerSideEncryption.KMS("keyId", myContext)) .build()); Assert.fail("exception should be thrown"); } diff --git a/api/src/test/java/io/minio/StatObjectArgsTest.java b/api/src/test/java/io/minio/StatObjectArgsTest.java index e40c419d7..b481f0c41 100644 --- a/api/src/test/java/io/minio/StatObjectArgsTest.java +++ b/api/src/test/java/io/minio/StatObjectArgsTest.java @@ -24,25 +24,25 @@ import org.junit.Test; public class StatObjectArgsTest { - @Test(expected = IllegalArgumentException.class) + @Test(expected = NullPointerException.class) public void testEmptyBuild() { StatObjectArgs.builder().build(); Assert.fail("exception should be thrown"); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = NullPointerException.class) public void testEmptyBucketBuild1() { StatObjectArgs.builder().object("myobject").build(); Assert.fail("exception should be thrown"); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = NullPointerException.class) public void testEmptyBucketBuild2() { StatObjectArgs.builder().object("myobject").bucket(null).build(); Assert.fail("exception should be thrown"); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = NullPointerException.class) public void testEmptyBucketBuild3() { StatObjectArgs.builder().bucket("mybucket").bucket(null).build(); Assert.fail("exception should be thrown"); @@ -54,7 +54,7 @@ public void testEmptyRegionBuild() { Assert.fail("exception should be thrown"); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = NullPointerException.class) public void testEmptyObjectBuild1() { StatObjectArgs.builder().object(null).build(); Assert.fail("exception should be thrown"); @@ -70,8 +70,8 @@ public void testEmptyObjectBuild2() { public void testBuild() throws NoSuchAlgorithmException, InvalidKeyException { KeyGenerator keyGen = KeyGenerator.getInstance("AES"); keyGen.init(256); - ServerSideEncryptionCustomerKey ssec = - new ServerSideEncryptionCustomerKey(keyGen.generateKey()); + ServerSideEncryption.CustomerKey ssec = + new ServerSideEncryption.CustomerKey(keyGen.generateKey()); StatObjectArgs args = StatObjectArgs.builder() .bucket("mybucket") diff --git a/examples/ComposeObject.java b/examples/ComposeObject.java index 3cad7faa2..d3ca691f1 100644 --- a/examples/ComposeObject.java +++ b/examples/ComposeObject.java @@ -18,7 +18,6 @@ import io.minio.ComposeSource; import io.minio.MinioClient; import io.minio.ServerSideEncryption; -import io.minio.ServerSideEncryptionCustomerKey; import io.minio.errors.MinioException; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -71,13 +70,13 @@ public static void main(String[] args) } { - ServerSideEncryptionCustomerKey srcSsec = - new ServerSideEncryptionCustomerKey( + ServerSideEncryption.CustomerKey srcSsec = + new ServerSideEncryption.CustomerKey( new SecretKeySpec( "01234567890123456789012345678901".getBytes(StandardCharsets.UTF_8), "AES")); ServerSideEncryption sse = - new ServerSideEncryptionCustomerKey( + new ServerSideEncryption.CustomerKey( new SecretKeySpec( "12345678912345678912345678912345".getBytes(StandardCharsets.UTF_8), "AES")); diff --git a/examples/CopyObject.java b/examples/CopyObject.java index 6b6508f2a..cc205e80f 100644 --- a/examples/CopyObject.java +++ b/examples/CopyObject.java @@ -19,9 +19,6 @@ import io.minio.CopySource; import io.minio.MinioClient; import io.minio.ServerSideEncryption; -import io.minio.ServerSideEncryptionCustomerKey; -import io.minio.ServerSideEncryptionKms; -import io.minio.ServerSideEncryptionS3; import io.minio.errors.MinioException; import java.io.IOException; import java.security.InvalidKeyException; @@ -51,14 +48,14 @@ public static void main(String[] args) KeyGenerator keyGen = KeyGenerator.getInstance("AES"); keyGen.init(256); - ServerSideEncryptionCustomerKey ssec = - new ServerSideEncryptionCustomerKey(keyGen.generateKey()); + ServerSideEncryption.CustomerKey ssec = + new ServerSideEncryption.CustomerKey(keyGen.generateKey()); Map myContext = new HashMap<>(); myContext.put("key1", "value1"); - ServerSideEncryption sseKms = new ServerSideEncryptionKms("Key-Id", myContext); + ServerSideEncryption sseKms = new ServerSideEncryption.KMS("Key-Id", myContext); - ServerSideEncryption sseS3 = new ServerSideEncryptionS3(); + ServerSideEncryption sseS3 = new ServerSideEncryption.S3(); Map headers = new HashMap<>(); headers.put("Content-Type", "application/json"); diff --git a/examples/DownloadObject.java b/examples/DownloadObject.java index f357842d0..0402ce356 100644 --- a/examples/DownloadObject.java +++ b/examples/DownloadObject.java @@ -16,7 +16,7 @@ import io.minio.DownloadObjectArgs; import io.minio.MinioClient; -import io.minio.ServerSideEncryptionCustomerKey; +import io.minio.ServerSideEncryption; import io.minio.errors.MinioException; import java.io.IOException; import java.security.InvalidKeyException; @@ -56,8 +56,8 @@ public static void main(String[] args) { KeyGenerator keyGen = KeyGenerator.getInstance("AES"); keyGen.init(256); - ServerSideEncryptionCustomerKey ssec = - new ServerSideEncryptionCustomerKey(keyGen.generateKey()); + ServerSideEncryption.CustomerKey ssec = + new ServerSideEncryption.CustomerKey(keyGen.generateKey()); // Download SSE-C encrypted 'my-objectname' from 'my-bucketname' to 'my-filename' minioClient.downloadObject( diff --git a/examples/GetPresignedObjectUrl.java b/examples/GetPresignedObjectUrl.java index e154b88cb..bf8342e86 100644 --- a/examples/GetPresignedObjectUrl.java +++ b/examples/GetPresignedObjectUrl.java @@ -15,9 +15,9 @@ */ import io.minio.GetPresignedObjectUrlArgs; +import io.minio.Http; import io.minio.MinioClient; import io.minio.errors.MinioException; -import io.minio.http.Method; import java.io.IOException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; @@ -46,7 +46,7 @@ public static void main(String[] args) String url = minioClient.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() - .method(Method.DELETE) + .method(Http.Method.DELETE) .bucket("my-bucketname") .object("my-objectname") .expiry(60 * 60 * 24) diff --git a/examples/ListBuckets.java b/examples/ListBuckets.java index f0e15263d..aa297d755 100644 --- a/examples/ListBuckets.java +++ b/examples/ListBuckets.java @@ -18,7 +18,7 @@ import io.minio.MinioClient; import io.minio.Result; import io.minio.errors.MinioException; -import io.minio.messages.Bucket; +import io.minio.messages.ListAllMyBucketsResult; import java.io.IOException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; @@ -43,9 +43,10 @@ public static void main(String[] args) // .build(); // List buckets we have atleast read access. - Iterable> results = minioClient.listBuckets(ListBucketsArgs.builder().build()); - for (Result result : results) { - Bucket bucket = result.get(); + Iterable> results = + minioClient.listBuckets(ListBucketsArgs.builder().build()); + for (Result result : results) { + ListAllMyBucketsResult.Bucket bucket = result.get(); System.out.println( String.format( "Bucket: %s, Region: %s, CreationDate: %s", diff --git a/examples/ListenBucketNotification.java b/examples/ListenBucketNotification.java index d0b5bc80b..3ef5ecc01 100644 --- a/examples/ListenBucketNotification.java +++ b/examples/ListenBucketNotification.java @@ -19,7 +19,6 @@ import io.minio.MinioClient; import io.minio.Result; import io.minio.errors.MinioException; -import io.minio.messages.Event; import io.minio.messages.NotificationRecords; import java.io.IOException; import java.security.InvalidKeyException; @@ -55,8 +54,9 @@ public static void main(String[] args) .build())) { while (ci.hasNext()) { NotificationRecords records = ci.next().get(); - Event event = records.events().get(0); - System.out.println(event.bucketName() + "/" + event.objectName() + " has been created"); + NotificationRecords.Event event = records.events().get(0); + System.out.println( + event.bucket().name() + "/" + event.object().key() + " has been created"); } } catch (IOException e) { System.out.println("Error occurred: " + e); diff --git a/examples/PresignedGetObject.java b/examples/PresignedGetObject.java index cf9c889d2..6e2edc5b0 100644 --- a/examples/PresignedGetObject.java +++ b/examples/PresignedGetObject.java @@ -15,9 +15,9 @@ */ import io.minio.GetPresignedObjectUrlArgs; +import io.minio.Http; import io.minio.MinioClient; import io.minio.errors.MinioException; -import io.minio.http.Method; import java.io.IOException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; @@ -46,7 +46,7 @@ public static void main(String[] args) String url = minioClient.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() - .method(Method.GET) + .method(Http.Method.GET) .bucket("my-bucketname") .object("my-objectname") .expiry(60 * 60 * 24) diff --git a/examples/PresignedPutObject.java b/examples/PresignedPutObject.java index e231de21e..d1f03af3b 100644 --- a/examples/PresignedPutObject.java +++ b/examples/PresignedPutObject.java @@ -15,9 +15,9 @@ */ import io.minio.GetPresignedObjectUrlArgs; +import io.minio.Http; import io.minio.MinioClient; import io.minio.errors.MinioException; -import io.minio.http.Method; import java.io.IOException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; @@ -52,7 +52,7 @@ public static void main(String[] args) String url = minioClient.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() - .method(Method.PUT) + .method(Http.Method.PUT) .bucket("my-bucketname") .object("my-objectname") .expiry(60 * 60 * 24) diff --git a/examples/PutObject.java b/examples/PutObject.java index 06fdd75ef..4c29bc886 100644 --- a/examples/PutObject.java +++ b/examples/PutObject.java @@ -17,9 +17,6 @@ import io.minio.MinioClient; import io.minio.PutObjectArgs; import io.minio.ServerSideEncryption; -import io.minio.ServerSideEncryptionCustomerKey; -import io.minio.ServerSideEncryptionKms; -import io.minio.ServerSideEncryptionS3; import io.minio.errors.MinioException; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -95,8 +92,8 @@ public static void main(String[] args) // Generate a new 256 bit AES key - This key must be remembered by the client. KeyGenerator keyGen = KeyGenerator.getInstance("AES"); keyGen.init(256); - ServerSideEncryptionCustomerKey ssec = - new ServerSideEncryptionCustomerKey(keyGen.generateKey()); + ServerSideEncryption.CustomerKey ssec = + new ServerSideEncryption.CustomerKey(keyGen.generateKey()); // Create encrypted object 'my-objectname' using SSE-C in 'my-bucketname' with content from // the input stream. @@ -115,7 +112,7 @@ public static void main(String[] args) Map myContext = new HashMap<>(); myContext.put("key1", "value1"); - ServerSideEncryption sseKms = new ServerSideEncryptionKms("Key-Id", myContext); + ServerSideEncryption sseKms = new ServerSideEncryption.KMS("Key-Id", myContext); // Create encrypted object 'my-objectname' using SSE-KMS in 'my-bucketname' with content // from the input stream. @@ -132,7 +129,7 @@ public static void main(String[] args) // Create a InputStream for object upload. ByteArrayInputStream bais = new ByteArrayInputStream(builder.toString().getBytes("UTF-8")); - ServerSideEncryption sseS3 = new ServerSideEncryptionS3(); + ServerSideEncryption sseS3 = new ServerSideEncryption.S3(); // Create encrypted object 'my-objectname' using SSE-S3 in 'my-bucketname' with content // from the input stream. diff --git a/examples/RemoveObjects.java b/examples/RemoveObjects.java index 11827c3a7..48d6e7b13 100644 --- a/examples/RemoveObjects.java +++ b/examples/RemoveObjects.java @@ -18,8 +18,8 @@ import io.minio.RemoveObjectsArgs; import io.minio.Result; import io.minio.errors.MinioException; -import io.minio.messages.DeleteError; -import io.minio.messages.DeleteObject; +import io.minio.messages.DeleteRequest; +import io.minio.messages.DeleteResult; import java.io.IOException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; @@ -45,15 +45,15 @@ public static void main(String[] args) // .credentials("YOUR-ACCESSKEY", "YOUR-SECRETACCESSKEY") // .build(); - List objects = new LinkedList<>(); - objects.add(new DeleteObject("my-objectname1")); - objects.add(new DeleteObject("my-objectname2")); - objects.add(new DeleteObject("my-objectname3")); - Iterable> results = + List objects = new LinkedList<>(); + objects.add(new DeleteRequest.Object("my-objectname1")); + objects.add(new DeleteRequest.Object("my-objectname2")); + objects.add(new DeleteRequest.Object("my-objectname3")); + Iterable> results = minioClient.removeObjects( RemoveObjectsArgs.builder().bucket("my-bucketname").objects(objects).build()); - for (Result result : results) { - DeleteError error = result.get(); + for (Result result : results) { + DeleteResult.Error error = result.get(); System.out.println( "Error in deleting object " + error.objectName() + "; " + error.message()); } diff --git a/examples/SelectObjectContent.java b/examples/SelectObjectContent.java index 247a08a1a..987237763 100644 --- a/examples/SelectObjectContent.java +++ b/examples/SelectObjectContent.java @@ -19,10 +19,8 @@ import io.minio.SelectObjectContentArgs; import io.minio.SelectResponseStream; import io.minio.errors.MinioException; -import io.minio.messages.FileHeaderInfo; import io.minio.messages.InputSerialization; import io.minio.messages.OutputSerialization; -import io.minio.messages.QuoteFields; import io.minio.messages.Stats; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -65,9 +63,11 @@ public static void main(String[] args) String sqlExpression = "select * from S3Object"; InputSerialization is = - new InputSerialization(null, false, null, null, FileHeaderInfo.USE, null, null, null); + InputSerialization.newCSV( + null, false, null, null, InputSerialization.FileHeaderInfo.USE, null, null, null); OutputSerialization os = - new OutputSerialization(null, null, null, QuoteFields.ASNEEDED, null); + OutputSerialization.newCSV( + null, null, null, OutputSerialization.QuoteFields.ASNEEDED, null); SelectResponseStream stream = minioClient.selectObjectContent( diff --git a/examples/SetBucketLifecycle.java b/examples/SetBucketLifecycle.java index 2b512a5c1..630f830cc 100644 --- a/examples/SetBucketLifecycle.java +++ b/examples/SetBucketLifecycle.java @@ -17,10 +17,8 @@ import io.minio.MinioClient; import io.minio.SetBucketLifecycleArgs; import io.minio.errors.MinioException; -import io.minio.messages.Expiration; +import io.minio.messages.Filter; import io.minio.messages.LifecycleConfiguration; -import io.minio.messages.LifecycleRule; -import io.minio.messages.RuleFilter; import io.minio.messages.Status; import java.io.IOException; import java.security.InvalidKeyException; @@ -48,13 +46,13 @@ public static void main(String[] args) // .credentials("YOUR-ACCESSKEY", "YOUR-SECRETACCESSKEY") // .build(); - List rules = new LinkedList<>(); + List rules = new LinkedList<>(); rules.add( - new LifecycleRule( + new LifecycleConfiguration.Rule( Status.ENABLED, null, - new Expiration((ZonedDateTime) null, 365, null), - new RuleFilter("logs/"), + new LifecycleConfiguration.Expiration((ZonedDateTime) null, 365, null), + new Filter("logs/"), "rule2", null, null, diff --git a/examples/SetBucketNotification.java b/examples/SetBucketNotification.java index a3f1cf496..90d137037 100644 --- a/examples/SetBucketNotification.java +++ b/examples/SetBucketNotification.java @@ -19,12 +19,10 @@ import io.minio.errors.MinioException; import io.minio.messages.EventType; import io.minio.messages.NotificationConfiguration; -import io.minio.messages.QueueConfiguration; import java.io.IOException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; -import java.util.LinkedList; -import java.util.List; +import java.util.Arrays; public class SetBucketNotification { /** MinioClient.setBucketNotification() example. */ @@ -45,22 +43,24 @@ public static void main(String[] args) // .credentials("YOUR-ACCESSKEY", "YOUR-SECRETACCESSKEY") // .build(); - NotificationConfiguration config = new NotificationConfiguration(); - - // Add a new SQS configuration. - List queueConfigurationList = new LinkedList<>(); - QueueConfiguration queueConfiguration = new QueueConfiguration(); - queueConfiguration.setQueue("arn:minio:sqs::1:webhook"); - - List eventList = new LinkedList<>(); - eventList.add(EventType.OBJECT_CREATED_PUT); - eventList.add(EventType.OBJECT_CREATED_COPY); - queueConfiguration.setEvents(eventList); - queueConfiguration.setPrefixRule("images"); - queueConfiguration.setSuffixRule("pg"); - - queueConfigurationList.add(queueConfiguration); - config.setQueueConfigurationList(queueConfigurationList); + NotificationConfiguration config = + new NotificationConfiguration( + null, + Arrays.asList( + new NotificationConfiguration.QueueConfiguration[] { + // Add a new SQS configuration. + new NotificationConfiguration.QueueConfiguration( + "arn:minio:sqs::1:webhook", + null, + Arrays.asList( + new String[] { + EventType.OBJECT_CREATED_PUT.toString(), + EventType.OBJECT_CREATED_COPY.toString() + }), + new NotificationConfiguration.Filter("images", "pg")) + }), + null, + null); // Set updated notification configuration. minioClient.setBucketNotification( diff --git a/examples/SetBucketReplication.java b/examples/SetBucketReplication.java index 5f35de2ce..4dceec983 100644 --- a/examples/SetBucketReplication.java +++ b/examples/SetBucketReplication.java @@ -17,12 +17,8 @@ import io.minio.MinioClient; import io.minio.SetBucketReplicationArgs; import io.minio.errors.MinioException; -import io.minio.messages.AndOperator; -import io.minio.messages.DeleteMarkerReplication; +import io.minio.messages.Filter; import io.minio.messages.ReplicationConfiguration; -import io.minio.messages.ReplicationDestination; -import io.minio.messages.ReplicationRule; -import io.minio.messages.RuleFilter; import io.minio.messages.Status; import java.io.IOException; import java.security.InvalidKeyException; @@ -55,20 +51,20 @@ public static void main(String[] args) tags.put("key1", "value1"); tags.put("key2", "value2"); - ReplicationRule rule = - new ReplicationRule( - new DeleteMarkerReplication(Status.DISABLED), - new ReplicationDestination( + ReplicationConfiguration.Rule rule = + new ReplicationConfiguration.Rule( + new ReplicationConfiguration.DeleteMarkerReplication(Status.DISABLED), + new ReplicationConfiguration.Destination( null, null, "REPLACE-WITH-ACTUAL-DESTINATION-BUCKET-ARN", null, null, null, null), null, - new RuleFilter(new AndOperator("TaxDocs", tags)), + new Filter(new Filter.And("TaxDocs", tags)), "rule1", null, 1, null, Status.ENABLED); - List rules = new LinkedList<>(); + List rules = new LinkedList<>(); rules.add(rule); ReplicationConfiguration config = diff --git a/examples/SetObjectLockConfiguration.java b/examples/SetObjectLockConfiguration.java index 716518c9f..d057554a5 100644 --- a/examples/SetObjectLockConfiguration.java +++ b/examples/SetObjectLockConfiguration.java @@ -18,7 +18,6 @@ import io.minio.SetObjectLockConfigurationArgs; import io.minio.errors.MinioException; import io.minio.messages.ObjectLockConfiguration; -import io.minio.messages.RetentionDurationDays; import io.minio.messages.RetentionMode; import java.io.IOException; import java.security.InvalidKeyException; @@ -45,7 +44,8 @@ public static void main(String[] args) // Declaring config with Retention mode as Compliance and duration as 100 days ObjectLockConfiguration config = - new ObjectLockConfiguration(RetentionMode.COMPLIANCE, new RetentionDurationDays(100)); + new ObjectLockConfiguration( + RetentionMode.COMPLIANCE, new ObjectLockConfiguration.RetentionDurationDays(100)); minioClient.setObjectLockConfiguration( SetObjectLockConfigurationArgs.builder() diff --git a/examples/StatObject.java b/examples/StatObject.java index f6a3e13d1..99bac0721 100644 --- a/examples/StatObject.java +++ b/examples/StatObject.java @@ -15,7 +15,7 @@ */ import io.minio.MinioClient; -import io.minio.ServerSideEncryptionCustomerKey; +import io.minio.ServerSideEncryption; import io.minio.StatObjectArgs; import io.minio.StatObjectResponse; import io.minio.errors.MinioException; @@ -46,8 +46,8 @@ public static void main(String[] args) KeyGenerator keyGen = KeyGenerator.getInstance("AES"); keyGen.init(256); - ServerSideEncryptionCustomerKey ssec = - new ServerSideEncryptionCustomerKey(keyGen.generateKey()); + ServerSideEncryption.CustomerKey ssec = + new ServerSideEncryption.CustomerKey(keyGen.generateKey()); String versionId = "ac38316c-fe14-4f96-9f76-8f675ae5a79e"; { diff --git a/examples/UploadObject.java b/examples/UploadObject.java index 35abedc48..26ca3601a 100644 --- a/examples/UploadObject.java +++ b/examples/UploadObject.java @@ -15,7 +15,7 @@ */ import io.minio.MinioClient; -import io.minio.ServerSideEncryptionCustomerKey; +import io.minio.ServerSideEncryption; import io.minio.UploadObjectArgs; import io.minio.errors.MinioException; import java.io.IOException; @@ -56,8 +56,8 @@ public static void main(String[] args) { KeyGenerator keyGen = KeyGenerator.getInstance("AES"); keyGen.init(256); - ServerSideEncryptionCustomerKey ssec = - new ServerSideEncryptionCustomerKey(keyGen.generateKey()); + ServerSideEncryption.CustomerKey ssec = + new ServerSideEncryption.CustomerKey(keyGen.generateKey()); // Upload 'my-filename' as object encrypted 'my-objectname' in 'my-bucketname'. minioClient.uploadObject( diff --git a/functional/FunctionalTest.java b/functional/FunctionalTest.java index 3f2234333..16153ce30 100644 --- a/functional/FunctionalTest.java +++ b/functional/FunctionalTest.java @@ -55,6 +55,7 @@ import io.minio.GetObjectRetentionArgs; import io.minio.GetObjectTagsArgs; import io.minio.GetPresignedObjectUrlArgs; +import io.minio.Http; import io.minio.IsObjectLegalHoldEnabledArgs; import io.minio.ListBucketsArgs; import io.minio.ListObjectsArgs; @@ -73,9 +74,6 @@ import io.minio.SelectObjectContentArgs; import io.minio.SelectResponseStream; import io.minio.ServerSideEncryption; -import io.minio.ServerSideEncryptionCustomerKey; -import io.minio.ServerSideEncryptionKms; -import io.minio.ServerSideEncryptionS3; import io.minio.SetBucketCorsArgs; import io.minio.SetBucketEncryptionArgs; import io.minio.SetBucketLifecycleArgs; @@ -96,38 +94,22 @@ import io.minio.Xml; import io.minio.admin.MinioAdminClient; import io.minio.errors.ErrorResponseException; -import io.minio.http.HttpUtils; -import io.minio.http.Method; +import io.minio.messages.AccessControlList; 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.DeleteRequest; import io.minio.messages.EventType; -import io.minio.messages.Expiration; -import io.minio.messages.FileHeaderInfo; -import io.minio.messages.GranteeType; +import io.minio.messages.Filter; import io.minio.messages.InputSerialization; import io.minio.messages.LifecycleConfiguration; -import io.minio.messages.LifecycleRule; +import io.minio.messages.ListAllMyBucketsResult; import io.minio.messages.NotificationConfiguration; 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; -import io.minio.messages.ReplicationDestination; -import io.minio.messages.ReplicationRule; import io.minio.messages.Retention; -import io.minio.messages.RetentionDuration; -import io.minio.messages.RetentionDurationDays; -import io.minio.messages.RetentionDurationYears; import io.minio.messages.RetentionMode; -import io.minio.messages.RuleFilter; import io.minio.messages.SseAlgorithm; import io.minio.messages.SseConfiguration; import io.minio.messages.Stats; @@ -205,8 +187,8 @@ public class FunctionalTest { private static MinioClient client = null; private static TestMinioAdminClient adminClientTests; - private static ServerSideEncryptionCustomerKey ssec = null; - private static ServerSideEncryption sseS3 = new ServerSideEncryptionS3(); + private static ServerSideEncryption.CustomerKey ssec = null; + private static ServerSideEncryption sseS3 = new ServerSideEncryption.S3(); private static ServerSideEncryption sseKms = null; static { @@ -220,7 +202,7 @@ public class FunctionalTest { try { KeyGenerator keyGen = KeyGenerator.getInstance("AES"); keyGen.init(256); - ssec = new ServerSideEncryptionCustomerKey(keyGen.generateKey()); + ssec = new ServerSideEncryption.CustomerKey(keyGen.generateKey()); } catch (InvalidKeyException | NoSuchAlgorithmException e) { throw new RuntimeException(e); } @@ -228,11 +210,7 @@ public class FunctionalTest { public static OkHttpClient newHttpClient() { try { - return HttpUtils.disableCertCheck( - HttpUtils.newDefaultHttpClient( - TimeUnit.MINUTES.toMillis(5), - TimeUnit.MINUTES.toMillis(5), - TimeUnit.MINUTES.toMillis(5))); + return Http.disableCertCheck(Http.newDefaultClient()); } catch (KeyManagementException | NoSuchAlgorithmException e) { throw new RuntimeException(e); } @@ -574,9 +552,9 @@ public static void listBuckets() throws Exception { expectedBucketNames.add(bucketName); List bucketNames = new LinkedList<>(); - for (Result result : + for (Result result : client.listBuckets(ListBucketsArgs.builder().maxBuckets(1).build())) { - Bucket bucket = result.get(); + ListAllMyBucketsResult.Bucket bucket = result.get(); if (expectedBucketNames.contains(bucket.name())) { bucketNames.add(bucket.name()); } @@ -1012,9 +990,9 @@ public static void testStatObject( try { client.putObject(args); try { - ServerSideEncryptionCustomerKey ssec = null; - if (args.sse() instanceof ServerSideEncryptionCustomerKey) { - ssec = (ServerSideEncryptionCustomerKey) args.sse(); + ServerSideEncryption.CustomerKey ssec = null; + if (args.sse() instanceof ServerSideEncryption.CustomerKey) { + ssec = (ServerSideEncryption.CustomerKey) args.sse(); } StatObjectResponse stat = client.statObject( @@ -1381,11 +1359,11 @@ public static List createObjects(String bucketName, int cou public static void removeObjects(String bucketName, List results) throws Exception { - List objects = + List objects = results.stream() .map( result -> { - return new DeleteObject(result.object(), result.versionId()); + return new DeleteRequest.Object(result.object(), result.versionId()); }) .collect(Collectors.toList()); for (Result r : @@ -1625,7 +1603,7 @@ public static void testGetPresignedObjectUrlForGet() throws Exception { testTags = "[GET]"; testGetPresignedUrl( GetPresignedObjectUrlArgs.builder() - .method(Method.GET) + .method(Http.Method.GET) .bucket(bucketName) .object(objectName) .build(), @@ -1634,7 +1612,7 @@ public static void testGetPresignedObjectUrlForGet() throws Exception { testTags = "[GET, expiry]"; testGetPresignedUrl( GetPresignedObjectUrlArgs.builder() - .method(Method.GET) + .method(Http.Method.GET) .bucket(bucketName) .object(objectName) .expiry(1, TimeUnit.DAYS) @@ -1646,7 +1624,7 @@ public static void testGetPresignedObjectUrlForGet() throws Exception { queryParams.put("response-content-type", "application/json"); testGetPresignedUrl( GetPresignedObjectUrlArgs.builder() - .method(Method.GET) + .method(Http.Method.GET) .bucket(bucketName) .object(objectName) .expiry(1, TimeUnit.DAYS) @@ -1702,7 +1680,7 @@ public static void testGetPresignedObjectUrlForPut() throws Exception { data, expectedChecksum, GetPresignedObjectUrlArgs.builder() - .method(Method.PUT) + .method(Http.Method.PUT) .bucket(bucketName) .object(objectName) .build()); @@ -1712,7 +1690,7 @@ public static void testGetPresignedObjectUrlForPut() throws Exception { data, expectedChecksum, GetPresignedObjectUrlArgs.builder() - .method(Method.PUT) + .method(Http.Method.PUT) .bucket(bucketName) .object(objectName) .expiry(1, TimeUnit.DAYS) @@ -1760,7 +1738,7 @@ public static void getPresignedPostFormData() throws Exception { String urlString = client.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() - .method(Method.GET) + .method(Http.Method.GET) .bucket(bucketName) .object("x") .build()); @@ -1815,9 +1793,9 @@ public static void testCopyObject( } else { client.copyObject(args); - ServerSideEncryptionCustomerKey ssec = null; - if (sse instanceof ServerSideEncryptionCustomerKey) { - ssec = (ServerSideEncryptionCustomerKey) sse; + ServerSideEncryption.CustomerKey ssec = null; + if (sse instanceof ServerSideEncryption.CustomerKey) { + ssec = (ServerSideEncryption.CustomerKey) sse; } client.statObject( StatObjectArgs.builder() @@ -2464,7 +2442,8 @@ public static void setObjectLockConfiguration() throws Exception { client.makeBucket(MakeBucketArgs.builder().bucket(bucketName).objectLock(true).build()); try { ObjectLockConfiguration config = - new ObjectLockConfiguration(RetentionMode.COMPLIANCE, new RetentionDurationDays(10)); + new ObjectLockConfiguration( + RetentionMode.COMPLIANCE, new ObjectLockConfiguration.RetentionDurationDays(10)); client.setObjectLockConfiguration( SetObjectLockConfigurationArgs.builder().bucket(bucketName).config(config).build()); } finally { @@ -2477,7 +2456,8 @@ public static void setObjectLockConfiguration() throws Exception { } public static void testGetObjectLockConfiguration( - String bucketName, RetentionMode mode, RetentionDuration duration) throws Exception { + String bucketName, RetentionMode mode, ObjectLockConfiguration.RetentionDuration duration) + throws Exception { ObjectLockConfiguration expectedConfig = new ObjectLockConfiguration(mode, duration); client.setObjectLockConfiguration( SetObjectLockConfigurationArgs.builder().bucket(bucketName).config(expectedConfig).build()); @@ -2506,9 +2486,13 @@ public static void getObjectLockConfiguration() throws Exception { client.makeBucket(MakeBucketArgs.builder().bucket(bucketName).objectLock(true).build()); try { testGetObjectLockConfiguration( - bucketName, RetentionMode.COMPLIANCE, new RetentionDurationDays(10)); + bucketName, + RetentionMode.COMPLIANCE, + new ObjectLockConfiguration.RetentionDurationDays(10)); testGetObjectLockConfiguration( - bucketName, RetentionMode.GOVERNANCE, new RetentionDurationYears(1)); + bucketName, + RetentionMode.GOVERNANCE, + new ObjectLockConfiguration.RetentionDurationYears(1)); } finally { client.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build()); } @@ -2533,7 +2517,8 @@ public static void deleteObjectLockConfiguration() throws Exception { client.deleteObjectLockConfiguration( DeleteObjectLockConfigurationArgs.builder().bucket(bucketName).build()); ObjectLockConfiguration config = - new ObjectLockConfiguration(RetentionMode.COMPLIANCE, new RetentionDurationDays(10)); + new ObjectLockConfiguration( + RetentionMode.COMPLIANCE, new ObjectLockConfiguration.RetentionDurationDays(10)); client.setObjectLockConfiguration( SetObjectLockConfigurationArgs.builder().bucket(bucketName).config(config).build()); client.deleteObjectLockConfiguration( @@ -2787,7 +2772,7 @@ public static void deleteBucketPolicy() throws Exception { } } - public static void testSetBucketLifecycle(String bucketName, LifecycleRule... rules) + public static void testSetBucketLifecycle(String bucketName, LifecycleConfiguration.Rule... rules) throws Exception { LifecycleConfiguration config = new LifecycleConfiguration(Arrays.asList(rules)); client.setBucketLifecycle( @@ -2807,11 +2792,11 @@ public static void setBucketLifecycle() throws Exception { try { testSetBucketLifecycle( bucketName, - new LifecycleRule( + new LifecycleConfiguration.Rule( Status.ENABLED, null, - new Expiration((ZonedDateTime) null, 365, null), - new RuleFilter("logs/"), + new LifecycleConfiguration.Expiration((ZonedDateTime) null, 365, null), + new Filter("logs/"), "rule2", null, null, @@ -2840,11 +2825,11 @@ public static void deleteBucketLifecycle() throws Exception { DeleteBucketLifecycleArgs.builder().bucket(bucketName).build()); testSetBucketLifecycle( bucketName, - new LifecycleRule( + new LifecycleConfiguration.Rule( Status.ENABLED, null, - new Expiration((ZonedDateTime) null, 365, null), - new RuleFilter("logs/"), + new LifecycleConfiguration.Expiration((ZonedDateTime) null, 365, null), + new Filter("logs/"), "rule2", null, null, @@ -2876,11 +2861,11 @@ public static void getBucketLifecycle() throws Exception { Assert.assertNull("config: expected: , got: ", config); testSetBucketLifecycle( bucketName, - new LifecycleRule( + new LifecycleConfiguration.Rule( Status.ENABLED, null, - new Expiration((ZonedDateTime) null, 365, null), - new RuleFilter("logs/"), + new LifecycleConfiguration.Expiration((ZonedDateTime) null, 365, null), + new Filter("logs/"), "rule2", null, null, @@ -2888,12 +2873,12 @@ public static void getBucketLifecycle() throws Exception { config = client.getBucketLifecycle(GetBucketLifecycleArgs.builder().bucket(bucketName).build()); Assert.assertNotNull("config: expected: , got: ", config); - List rules = config.rules(); + List rules = config.rules(); Assert.assertEquals( "config.rules().size(): expected: 1, got: " + config.rules().size(), 1, config.rules().size()); - LifecycleRule rule = rules.get(0); + LifecycleConfiguration.Rule rule = rules.get(0); Assert.assertEquals( "rule.status(): expected: " + Status.ENABLED + ", got: " + rule.status(), rule.status(), @@ -2913,11 +2898,11 @@ public static void getBucketLifecycle() throws Exception { testSetBucketLifecycle( bucketName, - new LifecycleRule( + new LifecycleConfiguration.Rule( Status.ENABLED, null, - new Expiration((ZonedDateTime) null, 365, null), - new RuleFilter(""), + new LifecycleConfiguration.Expiration((ZonedDateTime) null, 365, null), + new Filter(""), null, null, null, @@ -2961,21 +2946,23 @@ public static void setBucketNotification() throws Exception { String bucketName = getRandomName(); client.makeBucket(MakeBucketArgs.builder().bucket(bucketName).region(region).build()); try { - List eventList = new LinkedList<>(); - eventList.add(EventType.OBJECT_CREATED_PUT); - eventList.add(EventType.OBJECT_CREATED_COPY); - QueueConfiguration queueConfig = new QueueConfiguration(); - queueConfig.setQueue(sqsArn); - queueConfig.setEvents(eventList); - queueConfig.setPrefixRule("images"); - queueConfig.setSuffixRule("pg"); - - List queueConfigList = new LinkedList<>(); - queueConfigList.add(queueConfig); - - NotificationConfiguration config = new NotificationConfiguration(); - config.setQueueConfigurationList(queueConfigList); - + NotificationConfiguration config = + new NotificationConfiguration( + null, + Arrays.asList( + new NotificationConfiguration.QueueConfiguration[] { + new NotificationConfiguration.QueueConfiguration( + sqsArn, + null, + Arrays.asList( + new String[] { + EventType.OBJECT_CREATED_PUT.toString(), + EventType.OBJECT_CREATED_COPY.toString() + }), + new NotificationConfiguration.Filter("images", "pg")) + }), + null, + null); client.setBucketNotification( SetBucketNotificationArgs.builder().bucket(bucketName).config(config).build()); } finally { @@ -3003,18 +2990,19 @@ public static void getBucketNotification() throws Exception { String bucketName = getRandomName(); client.makeBucket(MakeBucketArgs.builder().bucket(bucketName).region(region).build()); try { - List eventList = new LinkedList<>(); - eventList.add(EventType.OBJECT_CREATED_PUT); - QueueConfiguration queueConfig = new QueueConfiguration(); - queueConfig.setQueue(sqsArn); - queueConfig.setEvents(eventList); - - List queueConfigList = new LinkedList<>(); - queueConfigList.add(queueConfig); - - NotificationConfiguration expectedConfig = new NotificationConfiguration(); - expectedConfig.setQueueConfigurationList(queueConfigList); - + NotificationConfiguration expectedConfig = + new NotificationConfiguration( + null, + Arrays.asList( + new NotificationConfiguration.QueueConfiguration[] { + new NotificationConfiguration.QueueConfiguration( + sqsArn, + null, + Arrays.asList(new String[] {EventType.OBJECT_CREATED_PUT.toString()}), + new NotificationConfiguration.Filter("images", "pg")) + }), + null, + null); client.setBucketNotification( SetBucketNotificationArgs.builder().bucket(bucketName).config(expectedConfig).build()); @@ -3022,11 +3010,12 @@ public static void getBucketNotification() throws Exception { client.getBucketNotification( GetBucketNotificationArgs.builder().bucket(bucketName).build()); - if (config.queueConfigurationList().size() != 1 - || !sqsArn.equals(config.queueConfigurationList().get(0).queue()) - || config.queueConfigurationList().get(0).events().size() != 1 - || config.queueConfigurationList().get(0).events().get(0) - != EventType.OBJECT_CREATED_PUT) { + if (config.queueConfigurations().size() != 1 + || !sqsArn.equals(config.queueConfigurations().get(0).queue()) + || config.queueConfigurations().get(0).events().size() != 1 + || !EventType.OBJECT_CREATED_PUT + .toString() + .equals(config.queueConfigurations().get(0).events().get(0))) { System.out.println( "config: expected: " + Xml.marshal(expectedConfig) + ", got: " + Xml.marshal(config)); } @@ -3055,21 +3044,23 @@ public static void deleteBucketNotification() throws Exception { String bucketName = getRandomName(); client.makeBucket(MakeBucketArgs.builder().bucket(bucketName).region(region).build()); try { - List eventList = new LinkedList<>(); - eventList.add(EventType.OBJECT_CREATED_PUT); - eventList.add(EventType.OBJECT_CREATED_COPY); - QueueConfiguration queueConfig = new QueueConfiguration(); - queueConfig.setQueue(sqsArn); - queueConfig.setEvents(eventList); - queueConfig.setPrefixRule("images"); - queueConfig.setSuffixRule("pg"); - - List queueConfigList = new LinkedList<>(); - queueConfigList.add(queueConfig); - - NotificationConfiguration config = new NotificationConfiguration(); - config.setQueueConfigurationList(queueConfigList); - + NotificationConfiguration config = + new NotificationConfiguration( + null, + Arrays.asList( + new NotificationConfiguration.QueueConfiguration[] { + new NotificationConfiguration.QueueConfiguration( + sqsArn, + null, + Arrays.asList( + new String[] { + EventType.OBJECT_CREATED_PUT.toString(), + EventType.OBJECT_CREATED_COPY.toString() + }), + new NotificationConfiguration.Filter("images", "pg")) + }), + null, + null); client.setBucketNotification( SetBucketNotificationArgs.builder().bucket(bucketName).config(config).build()); @@ -3079,7 +3070,7 @@ public static void deleteBucketNotification() throws Exception { config = client.getBucketNotification( GetBucketNotificationArgs.builder().bucket(bucketName).build()); - if (config.queueConfigurationList().size() != 0) { + if (config.queueConfigurations().size() != 0) { System.out.println("config: expected: , got: " + Xml.marshal(config)); } } finally { @@ -3128,8 +3119,8 @@ public static void listenBucketNotification() throws Exception { } boolean found = false; - for (Event event : records.events()) { - if (event.objectName().equals("prefix-random-suffix")) { + for (NotificationRecords.Event event : records.events()) { + if ("prefix-random-suffix".equals(event.object().key())) { found = true; break; } @@ -3183,9 +3174,11 @@ public static void selectObjectContent() throws Exception { .build()); InputSerialization is = - new InputSerialization(null, false, null, null, FileHeaderInfo.USE, null, null, null); + InputSerialization.newCSV( + null, false, null, null, InputSerialization.FileHeaderInfo.USE, null, null, null); OutputSerialization os = - new OutputSerialization(null, null, null, QuoteFields.ASNEEDED, null); + OutputSerialization.newCSV( + null, null, null, OutputSerialization.QuoteFields.ASNEEDED, null); responseStream = client.selectObjectContent( @@ -3206,18 +3199,15 @@ public static void selectObjectContent() throws Exception { Stats stats = responseStream.stats(); Assert.assertNotNull("stats is null", stats); - Assert.assertEquals( + Assert.assertTrue( "stats.bytesScanned mismatch; expected: 258, got: " + stats.bytesScanned(), - stats.bytesScanned(), - 256); - Assert.assertEquals( + stats.bytesScanned() == 256); + Assert.assertTrue( "stats.bytesProcessed mismatch; expected: 258, got: " + stats.bytesProcessed(), - stats.bytesProcessed(), - 256); - Assert.assertEquals( + stats.bytesProcessed() == 256); + Assert.assertTrue( "stats.bytesReturned mismatch; expected: 222, got: " + stats.bytesReturned(), - stats.bytesReturned(), - 222); + stats.bytesReturned() == 222); mintSuccessLog(methodName, testArgs, startTime); } catch (Exception e) { handleException(methodName, testArgs, startTime, e); @@ -3600,18 +3590,18 @@ public static void getObjectAcl() throws Exception { GetObjectAclArgs.builder().bucket(bucketName).object(objectName).build()); Assert.assertEquals( "granteeType: expected: " - + GranteeType.CANONICAL_USER + + AccessControlList.Type.CANONICAL_USER + ", got: " + policy.accessControlList().grants().get(0).grantee().type(), policy.accessControlList().grants().get(0).grantee().type(), - GranteeType.CANONICAL_USER); + AccessControlList.Type.CANONICAL_USER); Assert.assertEquals( "permission: expected: " - + Permission.FULL_CONTROL + + AccessControlList.Permission.FULL_CONTROL + ", got: " + policy.accessControlList().grants().get(0).permission(), policy.accessControlList().grants().get(0).permission(), - Permission.FULL_CONTROL); + AccessControlList.Permission.FULL_CONTROL); mintSuccessLog(methodName, null, startTime); } finally { client.removeObject( @@ -3686,19 +3676,20 @@ public static void setBucketReplication() throws Exception { tags.put("key1", "value1"); tags.put("key2", "value2"); - ReplicationRule rule = - new ReplicationRule( - new DeleteMarkerReplication(Status.DISABLED), - new ReplicationDestination(null, null, replicationBucketArn, null, null, null, null), + ReplicationConfiguration.Rule rule = + new ReplicationConfiguration.Rule( + new ReplicationConfiguration.DeleteMarkerReplication(Status.DISABLED), + new ReplicationConfiguration.Destination( + null, null, replicationBucketArn, null, null, null, null), null, - new RuleFilter(new AndOperator("TaxDocs", tags)), + new Filter(new Filter.And("TaxDocs", tags)), "rule1", null, 1, null, Status.ENABLED); - List rules = new LinkedList<>(); + List rules = new LinkedList<>(); rules.add(rule); ReplicationConfiguration config = new ReplicationConfiguration(replicationRole, rules); @@ -3735,19 +3726,20 @@ public static void getBucketReplication() throws Exception { tags.put("key1", "value1"); tags.put("key2", "value2"); - ReplicationRule rule = - new ReplicationRule( - new DeleteMarkerReplication(Status.DISABLED), - new ReplicationDestination(null, null, replicationBucketArn, null, null, null, null), + ReplicationConfiguration.Rule rule = + new ReplicationConfiguration.Rule( + new ReplicationConfiguration.DeleteMarkerReplication(Status.DISABLED), + new ReplicationConfiguration.Destination( + null, null, replicationBucketArn, null, null, null, null), null, - new RuleFilter(new AndOperator("TaxDocs", tags)), + new Filter(new Filter.And("TaxDocs", tags)), "rule1", null, 1, null, Status.ENABLED); - List rules = new LinkedList<>(); + List rules = new LinkedList<>(); rules.add(rule); config = new ReplicationConfiguration(replicationRole, rules); @@ -3785,19 +3777,20 @@ public static void deleteBucketReplication() throws Exception { tags.put("key1", "value1"); tags.put("key2", "value2"); - ReplicationRule rule = - new ReplicationRule( - new DeleteMarkerReplication(Status.DISABLED), - new ReplicationDestination(null, null, replicationBucketArn, null, null, null, null), + ReplicationConfiguration.Rule rule = + new ReplicationConfiguration.Rule( + new ReplicationConfiguration.DeleteMarkerReplication(Status.DISABLED), + new ReplicationConfiguration.Destination( + null, null, replicationBucketArn, null, null, null, null), null, - new RuleFilter(new AndOperator("TaxDocs", tags)), + new Filter(new Filter.And("TaxDocs", tags)), "rule1", null, 1, null, Status.ENABLED); - List rules = new LinkedList<>(); + List rules = new LinkedList<>(); rules.add(rule); ReplicationConfiguration config = new ReplicationConfiguration(replicationRole, rules); @@ -4224,7 +4217,7 @@ public static void main(String[] args) throws Exception { if (kmsKeyName != null) { Map myContext = new HashMap<>(); myContext.put("key1", "value1"); - sseKms = new ServerSideEncryptionKms(kmsKeyName, myContext); + sseKms = new ServerSideEncryption.KMS(kmsKeyName, myContext); } int exitValue = 0;