Skip to content

Commit 0f533e8

Browse files
authored
Add missing fields as per S3 specification (#1618)
Signed-off-by: Bala.FA <bala@minio.io>
1 parent e17ef94 commit 0f533e8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1074
-424
lines changed
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2025 MinIO, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.minio;
18+
19+
import io.minio.messages.CompleteMultipartUploadResult;
20+
import io.minio.messages.CopyObjectResult;
21+
import okhttp3.Headers;
22+
23+
/** Response class of any APIs doing object/part upload. */
24+
public class GenericUploadResponse extends GenericResponse {
25+
private String etag;
26+
private String checksumCRC32;
27+
private String checksumCRC32C;
28+
private String checksumCRC64NVME;
29+
private String checksumSHA1;
30+
private String checksumSHA256;
31+
private String checksumType;
32+
33+
public GenericUploadResponse(
34+
Headers headers, String bucket, String region, String object, String etag) {
35+
super(headers, bucket, region, object);
36+
this.etag = etag;
37+
if (headers != null) {
38+
this.checksumCRC32 = headers.get("x-amz-checksum-crc32");
39+
this.checksumCRC32C = headers.get("x-amz-checksum-crc32c");
40+
this.checksumCRC64NVME = headers.get("x-amz-checksum-crc64nvme");
41+
this.checksumSHA1 = headers.get("x-amz-checksum-sha1");
42+
this.checksumSHA256 = headers.get("x-amz-checksum-sha256");
43+
this.checksumType = headers.get("x-amz-checksum-type");
44+
}
45+
}
46+
47+
public GenericUploadResponse(
48+
Headers headers,
49+
String bucket,
50+
String region,
51+
String object,
52+
String etag,
53+
CopyObjectResult result) {
54+
super(headers, bucket, region, object);
55+
this.etag = etag;
56+
if (result != null) {
57+
this.checksumType = result.checksumType();
58+
this.checksumCRC32 = result.checksumCRC32();
59+
this.checksumCRC32C = result.checksumCRC32C();
60+
this.checksumCRC64NVME = result.checksumCRC64NVME();
61+
this.checksumSHA1 = result.checksumSHA1();
62+
this.checksumSHA256 = result.checksumSHA256();
63+
}
64+
}
65+
66+
public GenericUploadResponse(
67+
Headers headers,
68+
String bucket,
69+
String region,
70+
String object,
71+
String etag,
72+
CompleteMultipartUploadResult result) {
73+
super(headers, bucket, region, object);
74+
this.etag = etag;
75+
if (result != null) {
76+
this.checksumType = result.checksumType();
77+
this.checksumCRC32 = result.checksumCRC32();
78+
this.checksumCRC32C = result.checksumCRC32C();
79+
this.checksumCRC64NVME = result.checksumCRC64NVME();
80+
this.checksumSHA1 = result.checksumSHA1();
81+
this.checksumSHA256 = result.checksumSHA256();
82+
}
83+
}
84+
85+
public String etag() {
86+
return etag;
87+
}
88+
89+
public String checksumCRC32() {
90+
return checksumCRC32;
91+
}
92+
93+
public String checksumCRC32C() {
94+
return checksumCRC32C;
95+
}
96+
97+
public String checksumCRC64NVME() {
98+
return checksumCRC64NVME;
99+
}
100+
101+
public String checksumSHA1() {
102+
return checksumSHA1;
103+
}
104+
105+
public String checksumSHA256() {
106+
return checksumSHA256;
107+
}
108+
109+
public String checksumType() {
110+
return checksumType;
111+
}
112+
}

api/src/main/java/io/minio/ListBucketsArgs.java

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,31 @@
1616

1717
package io.minio;
1818

19+
import java.util.Objects;
20+
1921
/** Argument class of {@link MinioAsyncClient#listBuckets} and {@link MinioClient#listBuckets}. */
2022
public class ListBucketsArgs extends BaseArgs {
23+
private String bucketRegion;
24+
private int maxBuckets = 10000;
25+
private String prefix;
26+
private String continuationToken;
27+
28+
public String bucketRegion() {
29+
return bucketRegion;
30+
}
31+
32+
public int maxBuckets() {
33+
return maxBuckets;
34+
}
35+
36+
public String prefix() {
37+
return prefix;
38+
}
39+
40+
public String continuationToken() {
41+
return continuationToken;
42+
}
43+
2144
public static Builder builder() {
2245
return new Builder();
2346
}
@@ -26,5 +49,49 @@ public static Builder builder() {
2649
public static final class Builder extends BaseArgs.Builder<Builder, ListBucketsArgs> {
2750
@Override
2851
protected void validate(ListBucketsArgs args) {}
52+
53+
public Builder bucketRegion(String region) {
54+
validateNullOrNotEmptyString(region, "bucket region");
55+
operations.add(args -> args.bucketRegion = region);
56+
return this;
57+
}
58+
59+
public Builder maxBuckets(int maxBuckets) {
60+
if (maxBuckets < 1 || maxBuckets > 10000) {
61+
throw new IllegalArgumentException("max buckets must be between 1 and 10000");
62+
}
63+
64+
operations.add(args -> args.maxBuckets = maxBuckets);
65+
return this;
66+
}
67+
68+
public Builder prefix(String prefix) {
69+
validateNullOrNotEmptyString(prefix, "prefix");
70+
operations.add(args -> args.prefix = prefix);
71+
return this;
72+
}
73+
74+
public Builder continuationToken(String continuationToken) {
75+
validateNullOrNotEmptyString(continuationToken, "continuation token");
76+
operations.add(args -> args.continuationToken = continuationToken);
77+
return this;
78+
}
79+
}
80+
81+
@Override
82+
public boolean equals(Object o) {
83+
if (this == o) return true;
84+
if (!(o instanceof ListBucketsArgs)) return false;
85+
if (!super.equals(o)) return false;
86+
ListBucketsArgs that = (ListBucketsArgs) o;
87+
return Objects.equals(bucketRegion, that.bucketRegion)
88+
&& maxBuckets == that.maxBuckets
89+
&& Objects.equals(prefix, that.prefix)
90+
&& Objects.equals(continuationToken, that.continuationToken);
91+
}
92+
93+
@Override
94+
public int hashCode() {
95+
return Objects.hash(super.hashCode(), bucketRegion, maxBuckets, prefix, continuationToken);
2996
}
3097
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2025 MinIO, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.minio;
18+
19+
import io.minio.messages.ListAllMyBucketsResult;
20+
import okhttp3.Headers;
21+
22+
/** Response class of {@link S3Base#listBucketsAsync}. */
23+
public class ListBucketsResponse extends GenericResponse {
24+
private ListAllMyBucketsResult result;
25+
26+
public ListBucketsResponse(Headers headers, ListAllMyBucketsResult result) {
27+
super(headers, null, null, null);
28+
this.result = result;
29+
}
30+
31+
public ListAllMyBucketsResult result() {
32+
return result;
33+
}
34+
}

api/src/main/java/io/minio/MinioAsyncClient.java

Lines changed: 119 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,7 +1100,7 @@ public CompletableFuture<Void> removeObject(RemoveObjectArgs args)
11001100
* minioAsyncClient.removeObjects(
11011101
* RemoveObjectsArgs.builder().bucket("my-bucketname").objects(objects).build());
11021102
* for (Result<DeleteError> result : results) {
1103-
* DeleteError error = errorResult.get();
1103+
* DeleteError error = result.get();
11041104
* System.out.println(
11051105
* "Error in deleting object " + error.objectName() + "; " + error.message());
11061106
* }
@@ -1330,41 +1330,135 @@ public Iterable<Result<Item>> listObjects(ListObjectsArgs args) {
13301330
public CompletableFuture<List<Bucket>> listBuckets()
13311331
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
13321332
NoSuchAlgorithmException, XmlParserException {
1333-
return listBuckets(ListBucketsArgs.builder().build());
1333+
return listBucketsAsync(null, null, null, null, null, null)
1334+
.thenApply(
1335+
response -> {
1336+
return response.result().buckets();
1337+
});
13341338
}
13351339

13361340
/**
13371341
* Lists bucket information of all buckets.
13381342
*
13391343
* <pre>Example:{@code
1340-
* CompletableFuture<List<Bucket>> future =
1341-
* minioAsyncClient.listBuckets(ListBucketsArgs.builder().extraHeaders(headers).build());
1344+
* Iterable<Result<Bucket>> results = minioAsyncClient.listBuckets(ListBucketsArgs.builder().build());
1345+
* for (Result<Bucket> result : results) {
1346+
* Bucket bucket = result.get();
1347+
* System.out.println(String.format("Bucket: %s, Region: %s, CreationDate: %s", bucket.name(), bucket.bucketRegion(), bucket.creationDate()));
1348+
* }
13421349
* }</pre>
13431350
*
1344-
* @return {@link CompletableFuture}&lt;{@link List}&lt;{@link Bucket}&gt;&gt; object.
1345-
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
1346-
* @throws InternalException thrown to indicate internal library error.
1347-
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
1348-
* @throws IOException thrown to indicate I/O error on S3 operation.
1349-
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
1350-
* @throws XmlParserException thrown to indicate XML parsing error.
1351+
* @return {@link Iterable}&lt;{@link List}&lt;{@link Bucket}&gt;&gt; object.
13511352
*/
1352-
public CompletableFuture<List<Bucket>> listBuckets(ListBucketsArgs args)
1353-
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
1354-
NoSuchAlgorithmException, XmlParserException {
1355-
return executeGetAsync(args, null, null)
1356-
.thenApply(
1357-
response -> {
1353+
public Iterable<Result<Bucket>> listBuckets(ListBucketsArgs args) {
1354+
return new Iterable<Result<Bucket>>() {
1355+
@Override
1356+
public Iterator<Result<Bucket>> iterator() {
1357+
return new Iterator<Result<Bucket>>() {
1358+
private ListAllMyBucketsResult result = null;
1359+
private Result<Bucket> error = null;
1360+
private Iterator<Bucket> iterator = null;
1361+
private boolean completed = false;
1362+
1363+
private synchronized void populate() {
1364+
if (completed) return;
1365+
1366+
try {
1367+
this.iterator = new LinkedList<Bucket>().iterator();
13581368
try {
1359-
ListAllMyBucketsResult result =
1360-
Xml.unmarshal(ListAllMyBucketsResult.class, response.body().charStream());
1361-
return result.buckets();
1362-
} catch (XmlParserException e) {
1363-
throw new CompletionException(e);
1364-
} finally {
1365-
response.close();
1369+
ListBucketsResponse response =
1370+
listBucketsAsync(
1371+
args.bucketRegion(),
1372+
args.maxBuckets(),
1373+
args.prefix(),
1374+
(result == null)
1375+
? args.continuationToken()
1376+
: result.continuationToken(),
1377+
args.extraHeaders(),
1378+
args.extraQueryParams())
1379+
.get();
1380+
this.result = response.result();
1381+
} catch (InterruptedException e) {
1382+
throw new RuntimeException(e);
1383+
} catch (ExecutionException e) {
1384+
throwEncapsulatedException(e);
13661385
}
1367-
});
1386+
this.iterator = this.result.buckets().iterator();
1387+
} catch (ErrorResponseException
1388+
| InsufficientDataException
1389+
| InternalException
1390+
| InvalidKeyException
1391+
| InvalidResponseException
1392+
| IOException
1393+
| NoSuchAlgorithmException
1394+
| ServerException
1395+
| XmlParserException e) {
1396+
this.error = new Result<>(e);
1397+
completed = true;
1398+
}
1399+
}
1400+
1401+
@Override
1402+
public boolean hasNext() {
1403+
if (this.completed) return false;
1404+
1405+
if (this.error == null && this.iterator == null) {
1406+
populate();
1407+
}
1408+
1409+
if (this.error == null
1410+
&& !this.iterator.hasNext()
1411+
&& this.result.continuationToken() != null
1412+
&& !this.result.continuationToken().isEmpty()) {
1413+
populate();
1414+
}
1415+
1416+
if (this.error != null) return true;
1417+
if (this.iterator.hasNext()) return true;
1418+
1419+
this.completed = true;
1420+
return false;
1421+
}
1422+
1423+
@Override
1424+
public Result<Bucket> next() {
1425+
if (this.completed) throw new NoSuchElementException();
1426+
if (this.error == null && this.iterator == null) {
1427+
populate();
1428+
}
1429+
1430+
if (this.error == null
1431+
&& !this.iterator.hasNext()
1432+
&& this.result.continuationToken() != null
1433+
&& !this.result.continuationToken().isEmpty()) {
1434+
populate();
1435+
}
1436+
1437+
if (this.error != null) {
1438+
this.completed = true;
1439+
return this.error;
1440+
}
1441+
1442+
Bucket item = null;
1443+
if (this.iterator.hasNext()) {
1444+
item = this.iterator.next();
1445+
}
1446+
1447+
if (item != null) {
1448+
return new Result<>(item);
1449+
}
1450+
1451+
this.completed = true;
1452+
throw new NoSuchElementException();
1453+
}
1454+
1455+
@Override
1456+
public void remove() {
1457+
throw new UnsupportedOperationException();
1458+
}
1459+
};
1460+
}
1461+
};
13681462
}
13691463

13701464
/**

0 commit comments

Comments
 (0)