Skip to content

Commit c93f11b

Browse files
balamuruganaharshavardhana
authored andcommitted
api: add list object version 2 support and set to default. (#552)
Now ListObjects() API uses ReST API List Objects Version 2 (http://docs.aws.amazon.com/AmazonS3/latest/API/v2-RESTBucketGET.html) as a default. Added new method listObjects(String bucketName, String prefix, boolean recursive, boolean useVersion1) which can be used for ReST API List Objects Version 1 by passing true to useVersion1 flag. Fixes #413
1 parent e777ab7 commit c93f11b

File tree

5 files changed

+412
-29
lines changed

5 files changed

+412
-29
lines changed

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

Lines changed: 209 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import io.minio.messages.Item;
5252
import io.minio.messages.ListAllMyBucketsResult;
5353
import io.minio.messages.ListBucketResult;
54+
import io.minio.messages.ListBucketResultV1;
5455
import io.minio.messages.ListMultipartUploadsResult;
5556
import io.minio.messages.ListPartsResult;
5657
import io.minio.messages.Part;
@@ -2089,19 +2090,222 @@ public Iterable<Result<Item>> listObjects(final String bucketName, final String
20892090
*
20902091
* @see #listObjects(String bucketName)
20912092
* @see #listObjects(String bucketName, String prefix)
2093+
* @see #listObjects(String bucketName, String prefix, boolean recursive, boolean useVersion1)
20922094
*/
20932095
public Iterable<Result<Item>> listObjects(final String bucketName, final String prefix, final boolean recursive) {
2096+
return listObjects(bucketName, prefix, recursive, false);
2097+
}
2098+
2099+
2100+
/**
2101+
* Lists object information as {@code Iterable<Result><Item>>} in given bucket, prefix, recursive flag and S3 API
2102+
* version to use.
2103+
*
2104+
* </p><b>Example:</b><br>
2105+
* <pre>{@code Iterable<Result<Item>> myObjects = minioClient.listObjects("my-bucketname", "my-object-prefix", true,
2106+
* false);
2107+
* for (Result<Item> result : myObjects) {
2108+
* Item item = result.get();
2109+
* System.out.println(item.lastModified() + ", " + item.size() + ", " + item.objectName());
2110+
* } }</pre>
2111+
*
2112+
* @param bucketName Bucket name.
2113+
* @param prefix Prefix string. List objects whose name starts with `prefix`.
2114+
* @param recursive when false, emulates a directory structure where each listing returned is either a full object
2115+
* or part of the object's key up to the first '/'. All objects wit the same prefix up to the first
2116+
* '/' will be merged into one entry.
2117+
* @param useVersion1 If set, Amazon AWS S3 List Object V1 is used, else List Object V2 is used as default.
2118+
*
2119+
* @return an iterator of Result Items.
2120+
*
2121+
* @see #listObjects(String bucketName)
2122+
* @see #listObjects(String bucketName, String prefix)
2123+
* @see #listObjects(String bucketName, String prefix, boolean recursive)
2124+
*/
2125+
public Iterable<Result<Item>> listObjects(final String bucketName, final String prefix, final boolean recursive,
2126+
final boolean useVersion1) {
2127+
if (useVersion1) {
2128+
return listObjectsV1(bucketName, prefix, recursive);
2129+
}
2130+
2131+
return listObjectsV2(bucketName, prefix, recursive);
2132+
}
2133+
2134+
2135+
private Iterable<Result<Item>> listObjectsV2(final String bucketName, final String prefix, final boolean recursive) {
20942136
return new Iterable<Result<Item>>() {
20952137
@Override
20962138
public Iterator<Result<Item>> iterator() {
20972139
return new Iterator<Result<Item>>() {
2098-
private String lastObjectName;
20992140
private ListBucketResult listBucketResult;
21002141
private Result<Item> error;
21012142
private Iterator<Item> itemIterator;
21022143
private Iterator<Prefix> prefixIterator;
21032144
private boolean completed = false;
21042145

2146+
private synchronized void populate() {
2147+
String delimiter = "/";
2148+
if (recursive) {
2149+
delimiter = null;
2150+
}
2151+
2152+
String continuationToken = null;
2153+
if (this.listBucketResult != null && delimiter != null) {
2154+
continuationToken = listBucketResult.nextContinuationToken();
2155+
}
2156+
2157+
this.listBucketResult = null;
2158+
this.itemIterator = null;
2159+
this.prefixIterator = null;
2160+
2161+
try {
2162+
this.listBucketResult = listObjectsV2(bucketName, continuationToken, prefix, delimiter);
2163+
} catch (InvalidBucketNameException | NoSuchAlgorithmException | InsufficientDataException | IOException
2164+
| InvalidKeyException | NoResponseException | XmlPullParserException | ErrorResponseException
2165+
| InternalException e) {
2166+
this.error = new Result<>(null, e);
2167+
} finally {
2168+
if (this.listBucketResult != null) {
2169+
this.itemIterator = this.listBucketResult.contents().iterator();
2170+
this.prefixIterator = this.listBucketResult.commonPrefixes().iterator();
2171+
} else {
2172+
this.itemIterator = new LinkedList<Item>().iterator();
2173+
this.prefixIterator = new LinkedList<Prefix>().iterator();
2174+
}
2175+
}
2176+
}
2177+
2178+
@Override
2179+
public boolean hasNext() {
2180+
if (this.completed) {
2181+
return false;
2182+
}
2183+
2184+
if (this.error == null && this.itemIterator == null && this.prefixIterator == null) {
2185+
populate();
2186+
}
2187+
2188+
if (this.error == null && !this.itemIterator.hasNext() && !this.prefixIterator.hasNext()
2189+
&& this.listBucketResult.isTruncated()) {
2190+
populate();
2191+
}
2192+
2193+
if (this.error != null) {
2194+
return true;
2195+
}
2196+
2197+
if (this.itemIterator.hasNext()) {
2198+
return true;
2199+
}
2200+
2201+
if (this.prefixIterator.hasNext()) {
2202+
return true;
2203+
}
2204+
2205+
this.completed = true;
2206+
return false;
2207+
}
2208+
2209+
@Override
2210+
public Result<Item> next() {
2211+
if (this.completed) {
2212+
throw new NoSuchElementException();
2213+
}
2214+
2215+
if (this.error == null && this.itemIterator == null && this.prefixIterator == null) {
2216+
populate();
2217+
}
2218+
2219+
if (this.error == null && !this.itemIterator.hasNext() && !this.prefixIterator.hasNext()
2220+
&& this.listBucketResult.isTruncated()) {
2221+
populate();
2222+
}
2223+
2224+
if (this.error != null) {
2225+
this.completed = true;
2226+
return this.error;
2227+
}
2228+
2229+
if (this.itemIterator.hasNext()) {
2230+
Item item = this.itemIterator.next();
2231+
return new Result<>(item, null);
2232+
}
2233+
2234+
if (this.prefixIterator.hasNext()) {
2235+
Prefix prefix = this.prefixIterator.next();
2236+
Item item;
2237+
try {
2238+
item = new Item(prefix.prefix(), true);
2239+
} catch (XmlPullParserException e) {
2240+
// special case: ignore the error as we can't propagate the exception in next()
2241+
item = null;
2242+
}
2243+
2244+
return new Result<>(item, null);
2245+
}
2246+
2247+
this.completed = true;
2248+
throw new NoSuchElementException();
2249+
}
2250+
2251+
@Override
2252+
public void remove() {
2253+
throw new UnsupportedOperationException();
2254+
}
2255+
};
2256+
}
2257+
};
2258+
}
2259+
2260+
2261+
/**
2262+
* Returns {@link ListBucketResult} of given bucket, marker, prefix and delimiter.
2263+
*
2264+
* @param bucketName Bucket name.
2265+
* @param marker Marker string. List objects whose name is greater than `marker`.
2266+
* @param prefix Prefix string. List objects whose name starts with `prefix`.
2267+
* @param delimiter delimiter string. Group objects whose name contains `delimiter`.
2268+
*/
2269+
private ListBucketResult listObjectsV2(String bucketName, String continuationToken, String prefix, String delimiter)
2270+
throws InvalidBucketNameException, NoSuchAlgorithmException, InsufficientDataException, IOException,
2271+
InvalidKeyException, NoResponseException, XmlPullParserException, ErrorResponseException,
2272+
InternalException {
2273+
Map<String,String> queryParamMap = new HashMap<>();
2274+
queryParamMap.put("list-type", "2");
2275+
2276+
if (continuationToken != null) {
2277+
queryParamMap.put("continuation-token", continuationToken);
2278+
}
2279+
2280+
if (prefix != null) {
2281+
queryParamMap.put("prefix", prefix);
2282+
}
2283+
2284+
if (delimiter != null) {
2285+
queryParamMap.put("delimiter", delimiter);
2286+
}
2287+
2288+
HttpResponse response = executeGet(bucketName, null, null, queryParamMap);
2289+
2290+
ListBucketResult result = new ListBucketResult();
2291+
result.parseXml(response.body().charStream());
2292+
response.body().close();
2293+
return result;
2294+
}
2295+
2296+
2297+
private Iterable<Result<Item>> listObjectsV1(final String bucketName, final String prefix, final boolean recursive) {
2298+
return new Iterable<Result<Item>>() {
2299+
@Override
2300+
public Iterator<Result<Item>> iterator() {
2301+
return new Iterator<Result<Item>>() {
2302+
private String lastObjectName;
2303+
private ListBucketResultV1 listBucketResult;
2304+
private Result<Item> error;
2305+
private Iterator<Item> itemIterator;
2306+
private Iterator<Prefix> prefixIterator;
2307+
private boolean completed = false;
2308+
21052309
private synchronized void populate() {
21062310
String delimiter = "/";
21072311
if (recursive) {
@@ -2122,7 +2326,7 @@ private synchronized void populate() {
21222326
this.prefixIterator = null;
21232327

21242328
try {
2125-
this.listBucketResult = listObjects(bucketName, marker, prefix, delimiter, null);
2329+
this.listBucketResult = listObjectsV1(bucketName, marker, prefix, delimiter);
21262330
} catch (InvalidBucketNameException | NoSuchAlgorithmException | InsufficientDataException | IOException
21272331
| InvalidKeyException | NoResponseException | XmlPullParserException | ErrorResponseException
21282332
| InternalException e) {
@@ -2223,16 +2427,15 @@ public void remove() {
22232427

22242428

22252429
/**
2226-
* Returns {@link ListBucketResult} of given bucket, marker, prefix, delimiter and maxKeys.
2430+
* Returns {@link ListBucketResultV1} of given bucket, marker, prefix and delimiter.
22272431
*
22282432
* @param bucketName Bucket name.
22292433
* @param marker Marker string. List objects whose name is greater than `marker`.
22302434
* @param prefix Prefix string. List objects whose name starts with `prefix`.
22312435
* @param delimiter delimiter string. Group objects whose name contains `delimiter`.
22322436
* @param maxKeys Maximum number of entries to be returned.
22332437
*/
2234-
private ListBucketResult listObjects(String bucketName, String marker, String prefix, String delimiter,
2235-
Integer maxKeys)
2438+
private ListBucketResultV1 listObjectsV1(String bucketName, String marker, String prefix, String delimiter)
22362439
throws InvalidBucketNameException, NoSuchAlgorithmException, InsufficientDataException, IOException,
22372440
InvalidKeyException, NoResponseException, XmlPullParserException, ErrorResponseException,
22382441
InternalException {
@@ -2250,13 +2453,9 @@ private ListBucketResult listObjects(String bucketName, String marker, String pr
22502453
queryParamMap.put("delimiter", delimiter);
22512454
}
22522455

2253-
if (maxKeys != null) {
2254-
queryParamMap.put("max-keys", maxKeys.toString());
2255-
}
2256-
22572456
HttpResponse response = executeGet(bucketName, null, null, queryParamMap);
22582457

2259-
ListBucketResult result = new ListBucketResult();
2458+
ListBucketResultV1 result = new ListBucketResultV1();
22602459
result.parseXml(response.body().charStream());
22612460
response.body().close();
22622461
return result;

api/src/main/java/io/minio/messages/ListBucketResult.java

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Minio Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2015 Minio, Inc.
2+
* Minio Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2015, 2016, 2017 Minio, Inc.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,18 +25,22 @@
2525

2626

2727
/**
28-
* Helper class to parse Amazon AWS S3 response XML containing ListBucketResult information.
28+
* Helper class to parse Amazon AWS S3 response XML containing ListBucketResult Version 2 information.
2929
*/
3030
@SuppressWarnings({"SameParameterValue", "unused"})
3131
public class ListBucketResult extends XmlEntity {
3232
@Key("Name")
3333
private String name;
3434
@Key("Prefix")
3535
private String prefix;
36-
@Key("Marker")
37-
private String marker;
38-
@Key("NextMarker")
39-
private String nextMarker;
36+
@Key("ContinuationToken")
37+
private String continuationToken;
38+
@Key("NextContinuationToken")
39+
private String nextContinuationToken;
40+
@Key("StartAfter")
41+
private String startAfter;
42+
@Key("KeyCount")
43+
private String keyCount;
4044
@Key("MaxKeys")
4145
private int maxKeys;
4246
@Key("Delimiter")
@@ -55,14 +59,6 @@ public ListBucketResult() throws XmlPullParserException {
5559
}
5660

5761

58-
/**
59-
* Returns next marker.
60-
*/
61-
public String nextMarker() {
62-
return nextMarker;
63-
}
64-
65-
6662
/**
6763
* Returns bucket name.
6864
*/
@@ -80,10 +76,34 @@ public String prefix() {
8076

8177

8278
/**
83-
* Returns marker.
79+
* Returns continuation token.
80+
*/
81+
public String continuationToken() {
82+
return continuationToken;
83+
}
84+
85+
86+
/**
87+
* Returns next continuation token.
88+
*/
89+
public String nextContinuationToken() {
90+
return nextContinuationToken;
91+
}
92+
93+
94+
/**
95+
* Returns start after.
96+
*/
97+
public String startAfter() {
98+
return startAfter;
99+
}
100+
101+
102+
/**
103+
* Returns key count.
84104
*/
85-
public String marker() {
86-
return marker;
105+
public String keyCount() {
106+
return keyCount;
87107
}
88108

89109

0 commit comments

Comments
 (0)