com.ionoscloud.s3.ApiAsyncClient Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ionos-cloud-sdk-s3 Show documentation
Show all versions of ionos-cloud-sdk-s3 Show documentation
IONOS Java SDK for Amazon S3 Compatible Cloud Storage
The newest version!
package com.ionoscloud.s3;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.io.ByteStreams;
import com.ionoscloud.s3.credentials.Credentials;
import com.ionoscloud.s3.credentials.Provider;
import com.ionoscloud.s3.credentials.StaticProvider;
import com.ionoscloud.s3.errors.BucketPolicyTooLargeException;
import com.ionoscloud.s3.errors.ErrorResponseException;
import com.ionoscloud.s3.errors.InsufficientDataException;
import com.ionoscloud.s3.errors.InternalException;
import com.ionoscloud.s3.errors.InvalidResponseException;
import com.ionoscloud.s3.errors.ServerException;
import com.ionoscloud.s3.errors.XmlParserException;
import com.ionoscloud.s3.http.HttpUtils;
import com.ionoscloud.s3.http.Method;
import com.ionoscloud.s3.messages.Bucket;
import com.ionoscloud.s3.messages.CopyObjectResult;
import com.ionoscloud.s3.messages.CreateBucketConfiguration;
import com.ionoscloud.s3.messages.DeleteError;
import com.ionoscloud.s3.messages.DeleteObject;
import com.ionoscloud.s3.messages.Item;
import com.ionoscloud.s3.messages.LegalHold;
import com.ionoscloud.s3.messages.LifecycleConfiguration;
import com.ionoscloud.s3.messages.ListAllMyBucketsResult;
import com.ionoscloud.s3.messages.NotificationConfiguration;
import com.ionoscloud.s3.messages.NotificationRecords;
import com.ionoscloud.s3.messages.ObjectLockConfiguration;
import com.ionoscloud.s3.messages.Part;
import com.ionoscloud.s3.messages.ReplicationConfiguration;
import com.ionoscloud.s3.messages.Retention;
import com.ionoscloud.s3.messages.SelectObjectContentRequest;
import com.ionoscloud.s3.messages.SseConfiguration;
import com.ionoscloud.s3.messages.Tags;
import com.ionoscloud.s3.messages.VersioningConfiguration;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import java.util.regex.Matcher;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.xerial.snappy.SnappyFramedOutputStream;
/**
* Simple Storage Service (aka S3) client to perform bucket and object operations asynchronously.
*
* Bucket operations
*
*
* - Create, list and delete buckets.
*
- Put, get and delete bucket lifecycle configuration.
*
- Put, get and delete bucket policy configuration.
*
- Put, get and delete bucket encryption configuration.
*
- Put and get bucket default retention configuration.
*
- Put and get bucket notification configuration.
*
- Enable and disable bucket versioning.
*
*
* Object operations
*
*
* - Put, get, delete and list objects.
*
- Create objects by combining existing objects.
*
- Put and get object retention and legal hold.
*
- Filter object content by SQL statement.
*
*
* If access/secret keys are provided, all S3 operation requests are signed using AWS Signature
* Version 4; else they are performed anonymously.
*
*
Examples on using this library are available here.
*
*
Use {@code ApiAsyncClient.builder()} to create S3 client.
*
*
{@code
* // Create client with anonymous access.
* ApiAsyncClient apiAsyncClient =
* ApiAsyncClient.builder().endpoint(System.getenv("IONOS_API_URL")).build();
*
* // Create client with credentials.
* ApiAsyncClient apiAsyncClient =
* ApiAsyncClient.builder()
* .endpoint(System.getenv("IONOS_API_URL"))
* .credentials(System.getenv("IONOS_S3_ACCESS_KEY"), System.getenv("IONOS_S3_SECRET_KEY"))
* .build();
* }
*/
public class ApiAsyncClient extends S3Base {
private ApiAsyncClient(
HttpUrl baseUrl,
String awsS3Prefix,
String awsDomainSuffix,
boolean awsDualstack,
boolean useVirtualStyle,
String region,
Provider provider,
OkHttpClient httpClient) {
super(
baseUrl,
awsS3Prefix,
awsDomainSuffix,
awsDualstack,
useVirtualStyle,
region,
provider,
httpClient);
}
protected ApiAsyncClient(ApiAsyncClient client) {
super(client);
}
/**
* Gets information of an object asynchronously.
*
* Example:{@code
* // Get information of an object.
* CompletableFuture future =
* apiAsyncClient.statObject(
* StatObjectArgs.builder().bucket("my-bucketname").object("my-objectname").build());
*
* // Get information of SSE-C encrypted object.
* CompletableFuture future =
* apiAsyncClient.statObject(
* StatObjectArgs.builder()
* .bucket("my-bucketname")
* .object("my-objectname")
* .ssec(ssec)
* .build());
*
* // Get information of a versioned object.
* CompletableFuture future =
* apiAsyncClient.statObject(
* StatObjectArgs.builder()
* .bucket("my-bucketname")
* .object("my-objectname")
* .versionId("version-id")
* .build());
*
* // Get information of a SSE-C encrypted versioned object.
* CompletableFuture future =
* apiAsyncClient.statObject(
* StatObjectArgs.builder()
* .bucket("my-bucketname")
* .object("my-objectname")
* .versionId("version-id")
* .ssec(ssec)
* .build());
* }
*
* @param args {@link StatObjectArgs} object.
* @return {@link CompletableFuture}<{@link StatObjectResponse}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
* @see StatObjectResponse
*/
public CompletableFuture statObject(StatObjectArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
return super.statObjectAsync(args);
}
/**
* Gets data from offset to length of a SSE-C encrypted object asynchronously.
*
* Example:{@code
* CompletableFuture future = apiAsyncClient.getObject(
* GetObjectArgs.builder()
* .bucket("my-bucketname")
* .object("my-objectname")
* .offset(offset)
* .length(len)
* .ssec(ssec)
* .build()
* }
*
* @param args Object of {@link GetObjectArgs}
* @return {@link CompletableFuture}<{@link GetObjectResponse}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
* @see GetObjectResponse
*/
public CompletableFuture getObject(GetObjectArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
args.validateSsec(this.baseUrl);
return executeGetAsync(
args,
args.getHeaders(),
(args.versionId() != null) ? newMultimap("versionId", args.versionId()) : null)
.thenApply(
response -> {
return new GetObjectResponse(
response.headers(),
args.bucket(),
args.region(),
args.object(),
response.body().byteStream());
});
}
private void downloadObject(
String filename,
boolean overwrite,
StatObjectResponse statObjectResponse,
GetObjectResponse getObjectResponse)
throws IOException {
OutputStream os = null;
try {
Path filePath = Paths.get(filename);
String tempFilename =
filename + "." + S3Escaper.encode(statObjectResponse.etag()) + ".part.ionos";
Path tempFilePath = Paths.get(tempFilename);
if (Files.exists(tempFilePath)) Files.delete(tempFilePath);
os = Files.newOutputStream(tempFilePath, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
long bytesWritten = ByteStreams.copy(getObjectResponse, os);
if (bytesWritten != statObjectResponse.size()) {
throw new IOException(
tempFilename
+ ": unexpected data written. expected = "
+ statObjectResponse.size()
+ ", written = "
+ bytesWritten);
}
if (overwrite) {
Files.move(tempFilePath, filePath, StandardCopyOption.REPLACE_EXISTING);
} else {
Files.move(tempFilePath, filePath);
}
} finally {
getObjectResponse.close();
if (os != null) os.close();
}
}
/**
* Downloads data of a SSE-C encrypted object to file.
*
* Example:{@code
* CompletableFuture future = apiAsyncClient.downloadObject(
* DownloadObjectArgs.builder()
* .bucket("my-bucketname")
* .object("my-objectname")
* .ssec(ssec)
* .filename("my-filename")
* .build());
* }
*
* @param args Object of {@link DownloadObjectArgs}
* @return {@link CompletableFuture}<{@link Void}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture downloadObject(DownloadObjectArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
String filename = args.filename();
Path filePath = Paths.get(filename);
if (!args.overwrite() && Files.exists(filePath)) {
throw new IllegalArgumentException("Destination file " + filename + " already exists");
}
return statObjectAsync(new StatObjectArgs(args))
.thenCombine(
getObject(new GetObjectArgs(args)),
(statObjectResponse, getObjectResponse) -> {
try {
downloadObject(filename, args.overwrite(), statObjectResponse, getObjectResponse);
return null;
} catch (IOException e) {
throw new CompletionException(e);
}
})
.thenAccept(nullValue -> {});
}
/**
* Creates an object by server-side copying data from another object.
*
* Example:{@code
* // Create object "my-objectname" in bucket "my-bucketname" by copying from object
* // "my-objectname" in bucket "my-source-bucketname".
* CompletableFuture future = apiAsyncClient.copyObject(
* CopyObjectArgs.builder()
* .bucket("my-bucketname")
* .object("my-objectname")
* .source(
* CopySource.builder()
* .bucket("my-source-bucketname")
* .object("my-objectname")
* .build())
* .build());
*
* // Create object "my-objectname" in bucket "my-bucketname" by copying from object
* // "my-source-objectname" in bucket "my-source-bucketname".
* CompletableFuture future = apiAsyncClient.copyObject(
* CopyObjectArgs.builder()
* .bucket("my-bucketname")
* .object("my-objectname")
* .source(
* CopySource.builder()
* .bucket("my-source-bucketname")
* .object("my-source-objectname")
* .build())
* .build());
*
* // Create object "my-objectname" in bucket "my-bucketname" with SSE-KMS server-side
* // encryption by copying from object "my-objectname" in bucket "my-source-bucketname".
* CompletableFuture future = apiAsyncClient.copyObject(
* CopyObjectArgs.builder()
* .bucket("my-bucketname")
* .object("my-objectname")
* .source(
* CopySource.builder()
* .bucket("my-source-bucketname")
* .object("my-objectname")
* .build())
* .sse(sseKms) // Replace with actual key.
* .build());
*
* // Create object "my-objectname" in bucket "my-bucketname" with SSE-S3 server-side
* // encryption by copying from object "my-objectname" in bucket "my-source-bucketname".
* CompletableFuture future = apiAsyncClient.copyObject(
* CopyObjectArgs.builder()
* .bucket("my-bucketname")
* .object("my-objectname")
* .source(
* CopySource.builder()
* .bucket("my-source-bucketname")
* .object("my-objectname")
* .build())
* .sse(sseS3) // Replace with actual key.
* .build());
*
* // Create object "my-objectname" in bucket "my-bucketname" with SSE-C server-side encryption
* // by copying from object "my-objectname" in bucket "my-source-bucketname".
* CompletableFuture future = apiAsyncClient.copyObject(
* CopyObjectArgs.builder()
* .bucket("my-bucketname")
* .object("my-objectname")
* .source(
* CopySource.builder()
* .bucket("my-source-bucketname")
* .object("my-objectname")
* .build())
* .sse(ssec) // Replace with actual key.
* .build());
*
* // Create object "my-objectname" in bucket "my-bucketname" by copying from SSE-C encrypted
* // object "my-source-objectname" in bucket "my-source-bucketname".
* CompletableFuture future = apiAsyncClient.copyObject(
* CopyObjectArgs.builder()
* .bucket("my-bucketname")
* .object("my-objectname")
* .source(
* CopySource.builder()
* .bucket("my-source-bucketname")
* .object("my-source-objectname")
* .ssec(ssec) // Replace with actual key.
* .build())
* .build());
*
* // Create object "my-objectname" in bucket "my-bucketname" with custom headers conditionally
* // by copying from object "my-objectname" in bucket "my-source-bucketname".
* CompletableFuture future = apiAsyncClient.copyObject(
* CopyObjectArgs.builder()
* .bucket("my-bucketname")
* .object("my-objectname")
* .source(
* CopySource.builder()
* .bucket("my-source-bucketname")
* .object("my-objectname")
* .matchETag(etag) // Replace with actual etag.
* .build())
* .headers(headers) // Replace with actual headers.
* .build());
* }
*
* @param args {@link CopyObjectArgs} object.
* @return {@link CompletableFuture}<{@link ObjectWriteResponse}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture copyObject(CopyObjectArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
args.validateSse(this.baseUrl);
return CompletableFuture.supplyAsync(
() -> args.source().offset() != null && args.source().length() != null)
.thenCompose(
condition -> {
if (condition) {
try {
return statObjectAsync(new StatObjectArgs((ObjectReadArgs) args.source()));
} catch (InsufficientDataException
| InternalException
| InvalidKeyException
| IOException
| NoSuchAlgorithmException
| XmlParserException e) {
throw new CompletionException(e);
}
}
return CompletableFuture.completedFuture(null);
})
.thenApply(stat -> (stat == null) ? (long) -1 : stat.size())
.thenCompose(
size -> {
if (args.source().offset() != null
|| args.source().length() != null
|| size > ObjectWriteArgs.MAX_PART_SIZE) {
if (args.metadataDirective() != null
&& args.metadataDirective() == Directive.COPY) {
throw new IllegalArgumentException(
"COPY metadata directive is not applicable to source object size greater than"
+ " 5 GiB");
}
if (args.taggingDirective() != null && args.taggingDirective() == Directive.COPY) {
throw new IllegalArgumentException(
"COPY tagging directive is not applicable to source object size greater than"
+ " 5 GiB");
}
try {
return composeObject(new ComposeObjectArgs(args));
} catch (InsufficientDataException
| InternalException
| InvalidKeyException
| IOException
| NoSuchAlgorithmException
| XmlParserException e) {
throw new CompletionException(e);
}
}
return CompletableFuture.completedFuture(null);
})
.thenCompose(
objectWriteResponse -> {
if (objectWriteResponse != null) {
return CompletableFuture.completedFuture(objectWriteResponse);
}
Multimap headers = args.genHeaders();
if (args.metadataDirective() != null) {
headers.put("x-amz-metadata-directive", args.metadataDirective().name());
}
if (args.taggingDirective() != null) {
headers.put("x-amz-tagging-directive", args.taggingDirective().name());
}
headers.putAll(args.source().genCopyHeaders());
try {
return executePutAsync(args, headers, null, null, 0)
.thenApply(
response -> {
try {
CopyObjectResult result =
Xml.unmarshal(CopyObjectResult.class, response.body().charStream());
return new ObjectWriteResponse(
response.headers(),
args.bucket(),
args.region(),
args.object(),
result.etag(),
response.header("x-amz-version-id"));
} catch (XmlParserException e) {
throw new CompletionException(e);
} finally {
response.close();
}
});
} catch (InsufficientDataException
| InternalException
| InvalidKeyException
| IOException
| NoSuchAlgorithmException
| XmlParserException e) {
throw new CompletionException(e);
}
});
}
private CompletableFuture uploadPartCopy(
String bucketName,
String region,
String objectName,
String uploadId,
int partNumber,
Multimap headers,
Part[] parts)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
return uploadPartCopyAsync(bucketName, region, objectName, uploadId, partNumber, headers, null)
.thenApply(
uploadPartCopyResponse -> {
parts[partNumber - 1] = new Part(partNumber, uploadPartCopyResponse.result().etag());
return parts;
});
}
/**
* Creates an object by combining data from different source objects using server-side copy.
*
* Example:{@code
* List sourceObjectList = new ArrayList();
*
* sourceObjectList.add(
* ComposeSource.builder().bucket("my-job-bucket").object("my-objectname-part-one").build());
* sourceObjectList.add(
* ComposeSource.builder().bucket("my-job-bucket").object("my-objectname-part-two").build());
* sourceObjectList.add(
* ComposeSource.builder().bucket("my-job-bucket").object("my-objectname-part-three").build());
*
* // Create my-bucketname/my-objectname by combining source object list.
* CompletableFuture future = apiAsyncClient.composeObject(
* ComposeObjectArgs.builder()
* .bucket("my-bucketname")
* .object("my-objectname")
* .sources(sourceObjectList)
* .build());
*
* // Create my-bucketname/my-objectname with user metadata by combining source object
* // list.
* Map userMetadata = new HashMap<>();
* userMetadata.put("My-Project", "Project One");
* CompletableFuture future = apiAsyncClient.composeObject(
* ComposeObjectArgs.builder()
* .bucket("my-bucketname")
* .object("my-objectname")
* .sources(sourceObjectList)
* .userMetadata(userMetadata)
* .build());
*
* // Create my-bucketname/my-objectname with user metadata and server-side encryption
* // by combining source object list.
* CompletableFuture future = apiAsyncClient.composeObject(
* ComposeObjectArgs.builder()
* .bucket("my-bucketname")
* .object("my-objectname")
* .sources(sourceObjectList)
* .userMetadata(userMetadata)
* .ssec(sse)
* .build());
* }
*
* @param args {@link ComposeObjectArgs} object.
* @return {@link CompletableFuture}<{@link ObjectWriteResponse}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture composeObject(ComposeObjectArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
args.validateSse(this.baseUrl);
List sources = args.sources();
int[] partCount = {0};
String[] uploadIdCopy = {null};
return calculatePartCountAsync(sources)
.thenApply(
count -> {
partCount[0] = count;
return (count == 1
&& args.sources().get(0).offset() == null
&& args.sources().get(0).length() == null);
})
.thenCompose(
copyObjectFlag -> {
if (copyObjectFlag) {
try {
return copyObject(new CopyObjectArgs(args));
} catch (InsufficientDataException
| InternalException
| InvalidKeyException
| IOException
| NoSuchAlgorithmException
| XmlParserException e) {
throw new CompletionException(e);
}
}
return CompletableFuture.completedFuture(null);
})
.thenCompose(
objectWriteResponse -> {
if (objectWriteResponse != null) {
return CompletableFuture.completedFuture(objectWriteResponse);
}
CompletableFuture completableFuture =
CompletableFuture.supplyAsync(
() -> {
Multimap headers = newMultimap(args.extraHeaders());
headers.putAll(args.genHeaders());
return headers;
})
.thenCompose(
headers -> {
try {
return createMultipartUploadAsync(
args.bucket(),
args.region(),
args.object(),
headers,
args.extraQueryParams());
} catch (InsufficientDataException
| InternalException
| InvalidKeyException
| IOException
| NoSuchAlgorithmException
| XmlParserException e) {
throw new CompletionException(e);
}
})
.thenApply(
createMultipartUploadResponse -> {
String uploadId = createMultipartUploadResponse.result().uploadId();
uploadIdCopy[0] = uploadId;
return uploadId;
})
.thenCompose(
uploadId -> {
Multimap ssecHeaders = HashMultimap.create();
if (args.sse() != null
&& args.sse() instanceof ServerSideEncryptionCustomerKey) {
ssecHeaders.putAll(newMultimap(args.sse().headers()));
}
int partNumber = 0;
CompletableFuture future =
CompletableFuture.supplyAsync(
() -> {
return new Part[partCount[0]];
});
for (ComposeSource src : sources) {
long size = 0;
try {
size = src.objectSize();
} catch (InternalException e) {
throw new CompletionException(e);
}
if (src.length() != null) {
size = src.length();
} else if (src.offset() != null) {
size -= src.offset();
}
long offset = 0;
if (src.offset() != null) offset = src.offset();
final Multimap headers;
try {
headers = newMultimap(src.headers());
} catch (InternalException e) {
throw new CompletionException(e);
}
headers.putAll(ssecHeaders);
if (size <= ObjectWriteArgs.MAX_PART_SIZE) {
partNumber++;
if (src.length() != null) {
headers.put(
"x-amz-copy-source-range",
"bytes=" + offset + "-" + (offset + src.length() - 1));
} else if (src.offset() != null) {
headers.put(
"x-amz-copy-source-range",
"bytes=" + offset + "-" + (offset + size - 1));
}
final int partNum = partNumber;
future =
future.thenCompose(
parts -> {
try {
return uploadPartCopy(
args.bucket(),
args.region(),
args.object(),
uploadId,
partNum,
headers,
parts);
} catch (InsufficientDataException
| InternalException
| InvalidKeyException
| IOException
| NoSuchAlgorithmException
| XmlParserException e) {
throw new CompletionException(e);
}
});
continue;
}
while (size > 0) {
partNumber++;
long startBytes = offset;
long endBytes = startBytes + ObjectWriteArgs.MAX_PART_SIZE;
if (size < ObjectWriteArgs.MAX_PART_SIZE)
endBytes = startBytes + size;
Multimap headersCopy = newMultimap(headers);
headersCopy.put(
"x-amz-copy-source-range",
"bytes=" + startBytes + "-" + endBytes);
final int partNum = partNumber;
future =
future.thenCompose(
parts -> {
try {
return uploadPartCopy(
args.bucket(),
args.region(),
args.object(),
uploadId,
partNum,
headersCopy,
parts);
} catch (InsufficientDataException
| InternalException
| InvalidKeyException
| IOException
| NoSuchAlgorithmException
| XmlParserException e) {
throw new CompletionException(e);
}
});
offset = startBytes;
size -= (endBytes - startBytes);
}
}
return future;
})
.thenCompose(
parts -> {
try {
return completeMultipartUploadAsync(
args.bucket(),
args.region(),
args.object(),
uploadIdCopy[0],
parts,
null,
null);
} catch (InsufficientDataException
| InternalException
| InvalidKeyException
| IOException
| NoSuchAlgorithmException
| XmlParserException e) {
throw new CompletionException(e);
}
});
completableFuture.exceptionally(
e -> {
if (uploadIdCopy[0] != null) {
try {
abortMultipartUploadAsync(
args.bucket(),
args.region(),
args.object(),
uploadIdCopy[0],
null,
null)
.get();
} catch (InsufficientDataException
| InternalException
| InvalidKeyException
| IOException
| NoSuchAlgorithmException
| XmlParserException
| InterruptedException
| ExecutionException ex) {
throw new CompletionException(ex);
}
}
throw new CompletionException(e);
});
return completableFuture;
});
}
/**
* Gets presigned URL of an object for HTTP method, expiry time and custom request parameters.
*
* Example:{@code
* // Get presigned URL string to delete 'my-objectname' in 'my-bucketname' and its life time
* // is one day.
* String url =
* apiAsyncClient.getPresignedObjectUrl(
* GetPresignedObjectUrlArgs.builder()
* .method(Method.DELETE)
* .bucket("my-bucketname")
* .object("my-objectname")
* .expiry(24 * 60 * 60)
* .build());
* System.out.println(url);
*
* // Get presigned URL string to upload 'my-objectname' in 'my-bucketname'
* // with response-content-type as application/json and life time as one day.
* Map reqParams = new HashMap();
* reqParams.put("response-content-type", "application/json");
*
* String url =
* apiAsyncClient.getPresignedObjectUrl(
* GetPresignedObjectUrlArgs.builder()
* .method(Method.PUT)
* .bucket("my-bucketname")
* .object("my-objectname")
* .expiry(1, TimeUnit.DAYS)
* .extraQueryParams(reqParams)
* .build());
* System.out.println(url);
*
* // Get presigned URL string to download 'my-objectname' in 'my-bucketname' and its life time
* // is 2 hours.
* String url =
* apiAsyncClient.getPresignedObjectUrl(
* GetPresignedObjectUrlArgs.builder()
* .method(Method.GET)
* .bucket("my-bucketname")
* .object("my-objectname")
* .expiry(2, TimeUnit.HOURS)
* .build());
* System.out.println(url);
* }
*
* @param args {@link GetPresignedObjectUrlArgs} object.
* @return String - URL string.
* @throws ErrorResponseException thrown to indicate S3 service returned an error response.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws InvalidResponseException thrown to indicate S3 service returned invalid or no error
* response.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
* @throws ServerException
*/
public String getPresignedObjectUrl(GetPresignedObjectUrlArgs args)
throws ErrorResponseException, InsufficientDataException, InternalException,
InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException,
XmlParserException, ServerException {
checkArgs(args);
byte[] body =
(args.method() == Method.PUT || args.method() == Method.POST) ? HttpUtils.EMPTY_BODY : null;
Multimap queryParams = newMultimap(args.extraQueryParams());
if (args.versionId() != null) queryParams.put("versionId", args.versionId());
String region = null;
try {
region = getRegionAsync(args.bucket(), args.region()).get();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throwEncapsulatedException(e);
}
if (provider == null) {
HttpUrl url = buildUrl(args.method(), args.bucket(), args.object(), region, queryParams);
return url.toString();
}
Credentials creds = provider.fetch();
if (creds.sessionToken() != null) queryParams.put("X-Amz-Security-Token", creds.sessionToken());
HttpUrl url = buildUrl(args.method(), args.bucket(), args.object(), region, queryParams);
Request request =
createRequest(
url,
args.method(),
args.extraHeaders() == null ? null : httpHeaders(args.extraHeaders()),
body,
0,
creds);
url = Signer.presignV4(request, region, creds.accessKey(), creds.secretKey(), args.expiry());
return url.toString();
}
/**
* Gets form-data of {@link PostPolicy} of an object to upload its data using POST method.
*
* Example:{@code
* // Create new post policy for 'my-bucketname' with 7 days expiry from now.
* PostPolicy policy = new PostPolicy("my-bucketname", ZonedDateTime.now().plusDays(7));
*
* // Add condition that 'key' (object name) equals to 'my-objectname'.
* policy.addEqualsCondition("key", "my-objectname");
*
* // Add condition that 'Content-Type' starts with 'image/'.
* policy.addStartsWithCondition("Content-Type", "image/");
*
* // Add condition that 'content-length-range' is between 64kiB to 10MiB.
* policy.addContentLengthRangeCondition(64 * 1024, 10 * 1024 * 1024);
*
* Map formData = apiAsyncClient.getPresignedPostFormData(policy);
*
* // Upload an image using POST object with form-data.
* MultipartBody.Builder multipartBuilder = new MultipartBody.Builder();
* multipartBuilder.setType(MultipartBody.FORM);
* for (Map.Entry entry : formData.entrySet()) {
* multipartBuilder.addFormDataPart(entry.getKey(), entry.getValue());
* }
* multipartBuilder.addFormDataPart("key", "my-objectname");
* multipartBuilder.addFormDataPart("Content-Type", "image/png");
*
* // "file" must be added at last.
* multipartBuilder.addFormDataPart(
* "file", "my-objectname", RequestBody.create(new File("Pictures/avatar.png"), null));
*
* Request request =
* new Request.Builder()
* .url("")
* .post(multipartBuilder.build())
* .build();
* OkHttpClient httpClient = new OkHttpClient().newBuilder().build();
* Response response = httpClient.newCall(request).execute();
* if (response.isSuccessful()) {
* System.out.println("Pictures/avatar.png is uploaded successfully using POST object");
* } else {
* System.out.println("Failed to upload Pictures/avatar.png");
* }
* }
*
* @param policy Post policy of an object.
* @return {@code Map} - Contains form-data to upload an object using POST method.
* @throws ErrorResponseException thrown to indicate S3 service returned an error response.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws InvalidResponseException thrown to indicate S3 service returned invalid or no error
* response.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
* @see PostPolicy
*/
public Map getPresignedPostFormData(PostPolicy policy)
throws ErrorResponseException, InsufficientDataException, InternalException,
InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException,
ServerException, XmlParserException {
if (provider == null) {
throw new IllegalArgumentException(
"Anonymous access does not require presigned post form-data");
}
String region = null;
try {
region = getRegionAsync(policy.bucket(), null).get();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throwEncapsulatedException(e);
}
return policy.formData(provider.fetch(), region);
}
/**
* Removes an object.
*
* Example:{@code
* // Remove object.
* CompletableFuture future = apiAsyncClient.removeObject(
* RemoveObjectArgs.builder().bucket("my-bucketname").object("my-objectname").build());
*
* // Remove versioned object.
* CompletableFuture future = apiAsyncClient.removeObject(
* RemoveObjectArgs.builder()
* .bucket("my-bucketname")
* .object("my-versioned-objectname")
* .versionId("my-versionid")
* .build());
*
* // Remove versioned object bypassing Governance mode.
* CompletableFuture future = apiAsyncClient.removeObject(
* RemoveObjectArgs.builder()
* .bucket("my-bucketname")
* .object("my-versioned-objectname")
* .versionId("my-versionid")
* .bypassRetentionMode(true)
* .build());
* }
*
* @param args {@link RemoveObjectArgs} object.
* @return {@link CompletableFuture}<{@link Void}> object.
* @throws ErrorResponseException thrown to indicate S3 service returned an error response.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws InvalidResponseException thrown to indicate S3 service returned invalid or no error
* response.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture removeObject(RemoveObjectArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
return executeDeleteAsync(
args,
args.bypassGovernanceMode()
? newMultimap("x-amz-bypass-governance-retention", "true")
: null,
(args.versionId() != null) ? newMultimap("versionId", args.versionId()) : null)
.thenAccept(response -> response.close());
}
/**
* Removes multiple objects lazily. Its required to iterate the returned Iterable to perform
* removal.
*
* Example:{@code
* List objects = new LinkedList<>();
* objects.add(new DeleteObject("my-objectname1"));
* objects.add(new DeleteObject("my-objectname2"));
* objects.add(new DeleteObject("my-objectname3"));
* Iterable> results =
* apiAsyncClient.removeObjects(
* RemoveObjectsArgs.builder().bucket("my-bucketname").objects(objects).build());
* for (Result result : results) {
* DeleteError error = errorResult.get();
* System.out.println(
* "Error in deleting object " + error.objectName() + "; " + error.message());
* }
* }
*
* @param args {@link RemoveObjectsArgs} object.
* @return {@code Iterable>} - Lazy iterator contains object removal status.
*/
public Iterable> removeObjects(RemoveObjectsArgs args) {
checkArgs(args);
return new Iterable>() {
@Override
public Iterator> iterator() {
return new Iterator>() {
private Result error = null;
private Iterator errorIterator = null;
private boolean completed = false;
private Iterator objectIter = args.objects().iterator();
private void setError() {
error = null;
while (errorIterator.hasNext()) {
DeleteError deleteError = errorIterator.next();
if (!"NoSuchVersion".equals(deleteError.code())) {
error = new Result<>(deleteError);
break;
}
}
}
private synchronized void populate() {
if (completed) {
return;
}
try {
List objectList = new LinkedList<>();
while (objectIter.hasNext() && objectList.size() < 1000) {
objectList.add(objectIter.next());
}
completed = objectList.isEmpty();
if (completed) return;
DeleteObjectsResponse response = null;
try {
response =
deleteObjectsAsync(
args.bucket(),
args.region(),
objectList,
true,
args.bypassGovernanceMode(),
args.extraHeaders(),
args.extraQueryParams())
.get();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throwEncapsulatedException(e);
}
if (!response.result().errorList().isEmpty()) {
errorIterator = response.result().errorList().iterator();
setError();
completed = true;
}
} catch (ErrorResponseException
| InsufficientDataException
| InternalException
| InvalidKeyException
| InvalidResponseException
| IOException
| NoSuchAlgorithmException
| ServerException
| XmlParserException e) {
error = new Result<>(e);
completed = true;
}
}
@Override
public boolean hasNext() {
while (error == null && errorIterator == null && !completed) {
populate();
}
if (error == null && errorIterator != null) setError();
if (error != null) return true;
if (completed) return false;
errorIterator = null;
return hasNext();
}
@Override
public Result next() {
if (!hasNext()) throw new NoSuchElementException();
if (this.error != null) {
Result error = this.error;
this.error = null;
return error;
}
// This never happens.
throw new NoSuchElementException();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
};
}
/**
* Restores an object asynchronously.
*
* Example:{@code
* // Restore object.
* CompletableFuture future = apiAsyncClient.restoreObject(
* RestoreObjectArgs.builder()
* .bucket("my-bucketname")
* .object("my-objectname")
* .request(new RestoreRequest(null, null, null, null, null, null))
* .build());
*
* // Restore versioned object.
* CompletableFuture future = apiAsyncClient.restoreObject(
* RestoreObjectArgs.builder()
* .bucket("my-bucketname")
* .object("my-versioned-objectname")
* .versionId("my-versionid")
* .request(new RestoreRequest(null, null, null, null, null, null))
* .build());
* }
*
* @param args {@link RestoreObjectArgs} object.
* @return {@link CompletableFuture}<{@link Void}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture restoreObject(RestoreObjectArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
return executePostAsync(args, null, newMultimap("restore", ""), args.request())
.thenAccept(response -> response.close());
}
/**
* Lists objects information optionally with versions of a bucket. Supports both the versions 1
* and 2 of the S3 API. By default, the version 2 API
* is used.
* Version 1
* can be used by passing the optional argument {@code useVersion1} as {@code true}.
*
* Example:{@code
* // Lists objects information.
* Iterable> results = apiAsyncClient.listObjects(
* ListObjectsArgs.builder().bucket("my-bucketname").build());
*
* // Lists objects information recursively.
* Iterable> results = apiAsyncClient.listObjects(
* ListObjectsArgs.builder().bucket("my-bucketname").recursive(true).build());
*
* // Lists maximum 100 objects information whose names starts with 'E' and after
* // 'ExampleGuide.pdf'.
* Iterable> results = apiAsyncClient.listObjects(
* ListObjectsArgs.builder()
* .bucket("my-bucketname")
* .startAfter("ExampleGuide.pdf")
* .prefix("E")
* .maxKeys(100)
* .build());
*
* // Lists maximum 100 objects information with version whose names starts with 'E' and after
* // 'ExampleGuide.pdf'.
* Iterable> results = apiAsyncClient.listObjects(
* ListObjectsArgs.builder()
* .bucket("my-bucketname")
* .startAfter("ExampleGuide.pdf")
* .prefix("E")
* .maxKeys(100)
* .includeVersions(true)
* .build());
* }
*
* @param args Instance of {@link ListObjectsArgs} built using the builder
* @return {@code Iterable>} - Lazy iterator contains object information.
* @throws XmlParserException upon parsing response xml
*/
public Iterable> listObjects(ListObjectsArgs args) {
if (args.includeVersions() || args.versionIdMarker() != null) {
return listObjectVersions(args);
}
if (args.useApiVersion1()) {
return listObjectsV1(args);
}
return listObjectsV2(args);
}
/**
* Lists bucket information of all buckets.
*
* Example:{@code
* CompletableFuture> future = apiAsyncClient.listBuckets();
* }
*
* @return {@link CompletableFuture}<{@link List}<{@link Bucket}>> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture> listBuckets()
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
return listBuckets(ListBucketsArgs.builder().build());
}
/**
* Lists bucket information of all buckets.
*
* Example:{@code
* CompletableFuture> future =
* apiAsyncClient.listBuckets(ListBucketsArgs.builder().extraHeaders(headers).build());
* }
*
* @return {@link CompletableFuture}<{@link List}<{@link Bucket}>> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture> listBuckets(ListBucketsArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
return executeGetAsync(args, null, null)
.thenApply(
response -> {
try {
ListAllMyBucketsResult result =
Xml.unmarshal(ListAllMyBucketsResult.class, response.body().charStream());
return result.buckets();
} catch (XmlParserException e) {
throw new CompletionException(e);
} finally {
response.close();
}
});
}
/**
* Checks if a bucket exists.
*
* Example:{@code
* CompletableFuture future =
* apiAsyncClient.bucketExists(HeadBucketArgs.builder().bucket("my-bucketname").build());
* }
*
* @param args {@link HeadBucketArgs} object.
* @return {@link CompletableFuture}<{@link Boolean}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture bucketExists(HeadBucketArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
return executeHeadAsync(args, null, null)
.exceptionally(
e -> {
Throwable ex = e.getCause();
if (ex instanceof CompletionException) {
ex = ((CompletionException) ex).getCause();
}
if (ex instanceof ExecutionException) {
ex = ((ExecutionException) ex).getCause();
}
if (ex instanceof ErrorResponseException) {
if (((ErrorResponseException) ex).errorResponse().code().equals(NO_SUCH_BUCKET)) {
return null;
}
}
throw new CompletionException(ex);
})
.thenApply(
response -> {
try {
return response != null;
} finally {
if (response != null) response.close();
}
});
}
/**
* Creates a bucket with region and object lock.
*
* Example:{@code
* // Create bucket with default region.
* CompletableFuture future = apiAsyncClient.makeBucket(
* MakeBucketArgs.builder()
* .bucket("my-bucketname")
* .build());
*
* // Create bucket with specific region.
* CompletableFuture future = apiAsyncClient.makeBucket(
* MakeBucketArgs.builder()
* .bucket("my-bucketname")
* .region("us-west-1")
* .build());
*
* // Create object-lock enabled bucket with specific region.
* CompletableFuture future = apiAsyncClient.makeBucket(
* MakeBucketArgs.builder()
* .bucket("my-bucketname")
* .region("us-west-1")
* .objectLock(true)
* .build());
* }
*
* @param args Object with bucket name, region and lock functionality
* @return {@link CompletableFuture}<{@link Void}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture makeBucket(MakeBucketArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
String region = args.region();
if (this.region != null && !this.region.isEmpty()) {
// Error out if region does not match with region passed via constructor.
if (region != null && !region.equals(this.region)) {
throw new IllegalArgumentException(
"region must be " + this.region + ", but passed " + region);
}
region = this.region;
}
if (region == null) {
region = US_EAST_1;
}
Multimap headers =
args.objectLock() ? newMultimap("x-amz-bucket-object-lock-enabled", "true") : null;
final String location = region;
return executeAsync(
Method.PUT,
args.bucket(),
null,
location,
httpHeaders(merge(args.extraHeaders(), headers)),
args.extraQueryParams(),
location.equals(US_EAST_1) ? null : new CreateBucketConfiguration(location),
0)
.thenAccept(
response -> {
regionCache.put(args.bucket(), location);
response.close();
});
}
/**
* Sets versioning configuration of a bucket.
*
* Example:{@code
* CompletableFuture future = apiAsyncClient.putBucketVersioning(
* PutBucketVersioningArgs.builder().bucket("my-bucketname").config(config).build());
* }
*
* @param args {@link PutBucketVersioningArgs} object.
* @return {@link CompletableFuture}<{@link Void}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture putBucketVersioning(PutBucketVersioningArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
return executePutAsync(args, null, newMultimap("versioning", ""), args.config(), 0)
.thenAccept(response -> response.close());
}
/**
* Gets versioning configuration of a bucket.
*
* Example:{@code
* CompletableFuture future =
* apiAsyncClient.getBucketVersioning(
* GetBucketVersioningArgs.builder().bucket("my-bucketname").build());
* }
*
* @param args {@link GetBucketVersioningArgs} object.
* @return {@link CompletableFuture}<{@link VersioningConfiguration}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture getBucketVersioning(
GetBucketVersioningArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
return executeGetAsync(args, null, newMultimap("versioning", ""))
.thenApply(
response -> {
try {
return Xml.unmarshal(VersioningConfiguration.class, response.body().charStream());
} catch (XmlParserException e) {
throw new CompletionException(e);
} finally {
response.close();
}
});
}
/**
* Sets default object retention in a bucket.
*
* Example:{@code
* ObjectLockConfiguration config = new ObjectLockConfiguration(
* RetentionMode.COMPLIANCE, new RetentionDurationDays(100));
* CompletableFuture future = apiAsyncClient.putObjectLockConfiguration(
* PutObjectLockConfigurationArgs.builder().bucket("my-bucketname").config(config).build());
* }
*
* @param args {@link PutObjectLockConfigurationArgs} object.
* @return {@link CompletableFuture}<{@link Void}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture putObjectLockConfiguration(PutObjectLockConfigurationArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
return executePutAsync(args, null, newMultimap("object-lock", ""), args.config(), 0)
.thenAccept(response -> response.close());
}
/**
* Deletes default object retention in a bucket.
*
* Example:{@code
* CompletableFuture future = apiAsyncClient.deleteObjectLockConfiguration(
* DeleteObjectLockConfigurationArgs.builder().bucket("my-bucketname").build());
* }
*
* @param args {@link DeleteObjectLockConfigurationArgs} object.
* @return {@link CompletableFuture}<{@link Void}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture deleteObjectLockConfiguration(
DeleteObjectLockConfigurationArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
return executePutAsync(
args, null, newMultimap("object-lock", ""), new ObjectLockConfiguration(), 0)
.thenAccept(response -> response.close());
}
/**
* Gets default object retention in a bucket.
*
* Example:{@code
* CompletableFuture future =
* apiAsyncClient.getObjectLockConfiguration(
* GetObjectLockConfigurationArgs.builder().bucket("my-bucketname").build());
* }
*
* @param args {@link GetObjectLockConfigurationArgs} object.
* @return {@link CompletableFuture}<{@link ObjectLockConfiguration}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture getObjectLockConfiguration(
GetObjectLockConfigurationArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
return executeGetAsync(args, null, newMultimap("object-lock", ""))
.thenApply(
response -> {
try {
return Xml.unmarshal(ObjectLockConfiguration.class, response.body().charStream());
} catch (XmlParserException e) {
throw new CompletionException(e);
} finally {
response.close();
}
});
}
/**
* Sets retention configuration to an object.
*
* Example:{@code
* Retention retention = new Retention(
* RetentionMode.COMPLIANCE, ZonedDateTime.now().plusYears(1));
* CompletableFuture future = apiAsyncClient.putObjectRetention(
* PutObjectRetentionArgs.builder()
* .bucket("my-bucketname")
* .object("my-objectname")
* .config(config)
* .bypassGovernanceMode(true)
* .build());
* }
*
* @param args {@link PutObjectRetentionArgs} object.
* @return {@link CompletableFuture}<{@link Void}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture putObjectRetention(PutObjectRetentionArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
Multimap queryParams = newMultimap("retention", "");
if (args.versionId() != null) queryParams.put("versionId", args.versionId());
return executePutAsync(
args,
args.bypassGovernanceMode()
? newMultimap("x-amz-bypass-governance-retention", "True")
: null,
queryParams,
args.config(),
0)
.thenAccept(response -> response.close());
}
/**
* Gets retention configuration of an object.
*
* Example:{@code
* CompletableFuture future =
* apiAsyncClient.getObjectRetention(GetObjectRetentionArgs.builder()
* .bucket(bucketName)
* .object(objectName)
* .versionId(versionId)
* .build());
* }
*
* @param args {@link GetObjectRetentionArgs} object.
* @return {@link CompletableFuture}<{@link Retention}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture getObjectRetention(GetObjectRetentionArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
Multimap queryParams = newMultimap("retention", "");
if (args.versionId() != null) queryParams.put("versionId", args.versionId());
return executeGetAsync(args, null, queryParams)
.exceptionally(
e -> {
Throwable ex = e.getCause();
if (ex instanceof CompletionException) {
ex = ((CompletionException) ex).getCause();
}
if (ex instanceof ExecutionException) {
ex = ((ExecutionException) ex).getCause();
}
if (ex instanceof ErrorResponseException) {
if (((ErrorResponseException) ex)
.errorResponse()
.code()
.equals(NO_SUCH_OBJECT_LOCK_CONFIGURATION)) {
return null;
}
}
throw new CompletionException(ex);
})
.thenApply(
response -> {
if (response == null) return null;
try {
return Xml.unmarshal(Retention.class, response.body().charStream());
} catch (XmlParserException e) {
throw new CompletionException(e);
} finally {
response.close();
}
});
}
/**
* Enables legal hold on an object.
*
* Example:{@code
* CompletableFuture future = apiAsyncClient.putObjectLegalHold(
* PutObjectLegalHoldArgs.builder()
* .bucket("my-bucketname")
* .object("my-objectname")
* .versionId("object-versionId")
* .build());
* }
*
* @param args {@link PutObjectLegalHoldArgs} object.
* @return {@link CompletableFuture}<{@link Void}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture putObjectLegalHold(PutObjectLegalHoldArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
Multimap queryParams = newMultimap("legal-hold", "");
if (args.versionId() != null) queryParams.put("versionId", args.versionId());
return executePutAsync(args, null, queryParams, new LegalHold(true), 0)
.thenAccept(response -> response.close());
}
/**
* Disables legal hold on an object.
*
* Example:{@code
* CompletableFuture future = apiAsyncClient.disableObjectLegalHold(
* DisableObjectLegalHoldArgs.builder()
* .bucket("my-bucketname")
* .object("my-objectname")
* .versionId("object-versionId")
* .build());
* }
*
* @param args {@link DisableObjectLegalHoldArgs} object.
* @return {@link CompletableFuture}<{@link Void}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture disableObjectLegalHold(DisableObjectLegalHoldArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
Multimap queryParams = newMultimap("legal-hold", "");
if (args.versionId() != null) queryParams.put("versionId", args.versionId());
return executePutAsync(args, null, queryParams, new LegalHold(false), 0)
.thenAccept(response -> response.close());
}
/**
* Returns true if legal hold is enabled on an object.
*
* Example:{@code
* CompletableFuture future =
* s3Client.getObjectLegalHold(
* GetObjectLegalHoldArgs.builder()
* .bucket("my-bucketname")
* .object("my-objectname")
* .versionId("object-versionId")
* .build());
* }
*
* @param args {@link GetObjectLegalHoldArgs} object.
* @return {@link CompletableFuture}<{@link Boolean}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture getObjectLegalHold(GetObjectLegalHoldArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
Multimap queryParams = newMultimap("legal-hold", "");
if (args.versionId() != null) queryParams.put("versionId", args.versionId());
return executeGetAsync(args, null, queryParams)
.exceptionally(
e -> {
Throwable ex = e.getCause();
if (ex instanceof CompletionException) {
ex = ((CompletionException) ex).getCause();
}
if (ex instanceof ExecutionException) {
ex = ((ExecutionException) ex).getCause();
}
if (ex instanceof ErrorResponseException) {
if (((ErrorResponseException) ex)
.errorResponse()
.code()
.equals(NO_SUCH_OBJECT_LOCK_CONFIGURATION)) {
return null;
}
}
throw new CompletionException(ex);
})
.thenApply(
response -> {
if (response == null) return false;
try {
LegalHold result = Xml.unmarshal(LegalHold.class, response.body().charStream());
return result.status();
} catch (XmlParserException e) {
throw new CompletionException(e);
} finally {
response.close();
}
});
}
/**
* Removes an empty bucket using arguments
*
* Example:{@code
* CompletableFuture future =
* apiAsyncClient.deleteBucket(DeleteBucketArgs.builder().bucket("my-bucketname").build());
* }
*
* @param args {@link DeleteBucketArgs} bucket.
* @return {@link CompletableFuture}<{@link Void}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture deleteBucket(DeleteBucketArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
return executeDeleteAsync(args, null, null)
.thenAccept(response -> regionCache.remove(args.bucket()));
}
/**
* Uploads data from a stream to an object.
*
* Example:{@code
* // Upload known sized input stream.
* CompletableFuture future = apiAsyncClient.putObject(
* PutObjectArgs.builder().bucket("my-bucketname").object("my-objectname").stream(
* inputStream, size, -1)
* .contentType("video/mp4")
* .build());
*
* // Upload unknown sized input stream.
* CompletableFuture future = apiAsyncClient.putObject(
* PutObjectArgs.builder().bucket("my-bucketname").object("my-objectname").stream(
* inputStream, -1, 10485760)
* .contentType("video/mp4")
* .build());
*
* // Create object ends with '/' (also called as folder or directory).
* CompletableFuture future = apiAsyncClient.putObject(
* PutObjectArgs.builder().bucket("my-bucketname").object("path/to/").stream(
* new ByteArrayInputStream(new byte[] {}), 0, -1)
* .build());
*
* // Upload input stream with headers and user metadata.
* Map headers = new HashMap<>();
* headers.put("X-Amz-Storage-Class", "REDUCED_REDUNDANCY");
* Map userMetadata = new HashMap<>();
* userMetadata.put("My-Project", "Project One");
* CompletableFuture future = apiAsyncClient.putObject(
* PutObjectArgs.builder().bucket("my-bucketname").object("my-objectname").stream(
* inputStream, size, -1)
* .headers(headers)
* .userMetadata(userMetadata)
* .build());
*
* // Upload input stream with server-side encryption.
* CompletableFuture future = apiAsyncClient.putObject(
* PutObjectArgs.builder().bucket("my-bucketname").object("my-objectname").stream(
* inputStream, size, -1)
* .sse(sse)
* .build());
* }
*
* @param args {@link PutObjectArgs} object.
* @return {@link CompletableFuture}<{@link ObjectWriteResponse}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture putObject(PutObjectArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
args.validateSse(this.baseUrl);
return putObjectAsync(
args,
args.stream(),
args.objectSize(),
args.partSize(),
args.partCount(),
args.contentType());
}
/**
* Uploads data from a file to an object.
*
* Example:{@code
* // Upload an JSON file.
* CompletableFuture future = apiAsyncClient.postObject(
* PostObjectArgs.builder()
* .bucket("my-bucketname").object("my-objectname").filename("person.json").build());
*
* // Upload a video file.
* CompletableFuture future = apiAsyncClient.postObject(
* PostObjectArgs.builder()
* .bucket("my-bucketname")
* .object("my-objectname")
* .filename("my-video.avi")
* .contentType("video/mp4")
* .build());
* }
*
* @param args {@link PostObjectArgs} 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 postObject(PostObjectArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
args.validateSse(this.baseUrl);
final RandomAccessFile file = new RandomAccessFile(args.filename(), "r");
return putObjectAsync(
args, file, args.objectSize(), args.partSize(), args.partCount(), args.contentType())
.exceptionally(
e -> {
try {
file.close();
} catch (IOException ex) {
throw new CompletionException(ex);
}
Throwable ex = e.getCause();
if (ex instanceof CompletionException) {
ex = ((CompletionException) ex).getCause();
}
if (ex instanceof ExecutionException) {
ex = ((ExecutionException) ex).getCause();
}
throw new CompletionException(ex);
})
.thenApply(
objectWriteResponse -> {
try {
file.close();
} catch (IOException e) {
throw new CompletionException(e);
}
return objectWriteResponse;
});
}
/**
* Gets bucket policy configuration of a bucket.
*
* Example:{@code
* CompletableFuture future =
* apiAsyncClient.getBucketPolicy(
* GetBucketPolicyArgs.builder().bucket("my-bucketname").build());
* }
*
* @param args {@link GetBucketPolicyArgs} object.
* @return {@link CompletableFuture}<{@link String}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture getBucketPolicy(GetBucketPolicyArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
return executeGetAsync(args, null, newMultimap("policy", ""))
.exceptionally(
e -> {
Throwable ex = e.getCause();
if (ex instanceof CompletionException) {
ex = ((CompletionException) ex).getCause();
}
if (ex instanceof ExecutionException) {
ex = ((ExecutionException) ex).getCause();
}
if (ex instanceof ErrorResponseException) {
if (((ErrorResponseException) ex)
.errorResponse()
.code()
.equals(NO_SUCH_BUCKET_POLICY)) {
return null;
}
}
throw new CompletionException(ex);
})
.thenApply(
response -> {
if (response == null) return "";
try {
byte[] buf = new byte[MAX_BUCKET_POLICY_SIZE];
int bytesRead = 0;
bytesRead = response.body().byteStream().read(buf, 0, MAX_BUCKET_POLICY_SIZE);
if (bytesRead < 0) {
throw new CompletionException(
new IOException("unexpected EOF when reading bucket policy"));
}
// Read one byte extra to ensure only MAX_BUCKET_POLICY_SIZE data is sent by the
// server.
if (bytesRead == MAX_BUCKET_POLICY_SIZE) {
int byteRead = 0;
while (byteRead == 0) {
byteRead = response.body().byteStream().read();
if (byteRead < 0) {
break; // reached EOF which is fine.
}
if (byteRead > 0) {
throw new CompletionException(
new BucketPolicyTooLargeException(args.bucket()));
}
}
}
return new String(buf, 0, bytesRead, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new CompletionException(e);
} finally {
response.close();
}
});
}
/**
* Sets bucket policy configuration to a bucket.
*
* Example:{@code
* // Assume policyJson contains below JSON string;
* // {
* // "Statement": [
* // {
* // "Action": [
* // "s3:GetBucketLocation",
* // "s3:ListBucket"
* // ],
* // "Effect": "Allow",
* // "Principal": "*",
* // "Resource": "arn:aws:s3:::my-bucketname"
* // },
* // {
* // "Action": "s3:GetObject",
* // "Effect": "Allow",
* // "Principal": "*",
* // "Resource": "arn:aws:s3:::my-bucketname/myobject*"
* // }
* // ],
* // "Version": "2012-10-17"
* // }
* //
* CompletableFuture future = apiAsyncClient.putBucketPolicy(
* PutBucketPolicyArgs.builder().bucket("my-bucketname").config(policyJson).build());
* }
*
* @param args {@link PutBucketPolicyArgs} object.
* @return {@link CompletableFuture}<{@link Void}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture putBucketPolicy(PutBucketPolicyArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
return executePutAsync(
args,
newMultimap("Content-Type", "application/json"),
newMultimap("policy", ""),
args.config(),
0)
.thenAccept(response -> response.close());
}
/**
* Deletes bucket policy configuration to a bucket.
*
* Example:{@code
* CompletableFuture future =
* apiAsyncClient.deleteBucketPolicy(
* DeleteBucketPolicyArgs.builder().bucket("my-bucketname"));
* }
*
* @param args {@link DeleteBucketPolicyArgs} object.
* @return {@link CompletableFuture}<{@link Void}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture deleteBucketPolicy(DeleteBucketPolicyArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
return executeDeleteAsync(args, null, newMultimap("policy", ""))
.exceptionally(
e -> {
Throwable ex = e.getCause();
if (ex instanceof CompletionException) {
ex = ((CompletionException) ex).getCause();
}
if (ex instanceof ExecutionException) {
ex = ((ExecutionException) ex).getCause();
}
if (ex instanceof ErrorResponseException) {
if (((ErrorResponseException) ex)
.errorResponse()
.code()
.equals(NO_SUCH_BUCKET_POLICY)) {
return null;
}
}
throw new CompletionException(ex);
})
.thenAccept(
response -> {
if (response != null) response.close();
});
}
/**
* Sets lifecycle configuration to a bucket.
*
* Example:{@code
* List rules = new LinkedList<>();
* rules.add(
* new LifecycleRule(
* Status.ENABLED,
* null,
* new Expiration((ZonedDateTime) null, 365, null),
* new RuleFilter("logs/"),
* "rule2",
* null,
* null,
* null));
* LifecycleConfiguration config = new LifecycleConfiguration(rules);
* CompletableFuture future = apiAsyncClient.putBucketLifecycle(
* PutBucketLifecycleArgs.builder().bucket("my-bucketname").config(config).build());
* }
*
* @param args {@link PutBucketLifecycleArgs} object.
* @return {@link CompletableFuture}<{@link Void}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture putBucketLifecycle(PutBucketLifecycleArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
return executePutAsync(args, null, newMultimap("lifecycle", ""), args.config(), 0)
.thenAccept(response -> response.close());
}
/**
* Deletes lifecycle configuration of a bucket.
*
* Example:{@code
* CompletableFuture future = deleteBucketLifecycle(
* DeleteBucketLifecycleArgs.builder().bucket("my-bucketname").build());
* }
*
* @param args {@link DeleteBucketLifecycleArgs} object.
* @return {@link CompletableFuture}<{@link Void}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture deleteBucketLifecycle(DeleteBucketLifecycleArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
return executeDeleteAsync(args, null, newMultimap("lifecycle", ""))
.thenAccept(response -> response.close());
}
/**
* Gets lifecycle configuration of a bucket.
*
* Example:{@code
* CompletableFuture future =
* apiAsyncClient.getBucketLifecycle(
* GetBucketLifecycleArgs.builder().bucket("my-bucketname").build());
* }
*
* @param args {@link GetBucketLifecycleArgs} object.
* @return {@link LifecycleConfiguration} object.
* @return {@link CompletableFuture}<{@link LifecycleConfiguration}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture getBucketLifecycle(GetBucketLifecycleArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
return executeGetAsync(args, null, newMultimap("lifecycle", ""))
.exceptionally(
e -> {
Throwable ex = e.getCause();
if (ex instanceof CompletionException) {
ex = ((CompletionException) ex).getCause();
}
if (ex instanceof ExecutionException) {
ex = ((ExecutionException) ex).getCause();
}
if (ex instanceof ErrorResponseException) {
if (((ErrorResponseException) ex)
.errorResponse()
.code()
.equals("NoSuchLifecycleConfiguration")) {
return null;
}
}
throw new CompletionException(ex);
})
.thenApply(
response -> {
if (response == null) return null;
try {
return Xml.unmarshal(LifecycleConfiguration.class, response.body().charStream());
} catch (XmlParserException e) {
throw new CompletionException(e);
} finally {
response.close();
}
});
}
/**
* Gets notification configuration of a bucket.
*
* Example:{@code
* CompletableFuture future =
* apiAsyncClient.getBucketNotification(
* GetBucketNotificationArgs.builder().bucket("my-bucketname").build());
* }
*
* @param args {@link GetBucketNotificationArgs} object.
* @return {@link CompletableFuture}<{@link NotificationConfiguration}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture getBucketNotification(
GetBucketNotificationArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
return executeGetAsync(args, null, newMultimap("notification", ""))
.thenApply(
response -> {
try {
return Xml.unmarshal(NotificationConfiguration.class, response.body().charStream());
} catch (XmlParserException e) {
throw new CompletionException(e);
} finally {
response.close();
}
});
}
/**
* Sets notification configuration to a bucket.
*
* Example:{@code
* List eventList = new LinkedList<>();
* eventList.add(EventType.OBJECT_CREATED_PUT);
* eventList.add(EventType.OBJECT_CREATED_COPY);
*
* QueueConfiguration queueConfiguration = new QueueConfiguration();
* queueConfiguration.setQueue("arn:ionos:sqs::1:webhook");
* queueConfiguration.setEvents(eventList);
* queueConfiguration.setPrefixRule("images");
* queueConfiguration.setSuffixRule("pg");
*
* List queueConfigurationList = new LinkedList<>();
* queueConfigurationList.add(queueConfiguration);
*
* NotificationConfiguration config = new NotificationConfiguration();
* config.setQueueConfigurationList(queueConfigurationList);
*
* CompletableFuture future = apiAsyncClient.setBucketNotification(
* SetBucketNotificationArgs.builder().bucket("my-bucketname").config(config).build());
* }
*
* @param args {@link SetBucketNotificationArgs} object.
* @return {@link CompletableFuture}<{@link Void}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture setBucketNotification(SetBucketNotificationArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
return executePutAsync(args, null, newMultimap("notification", ""), args.config(), 0)
.thenAccept(response -> response.close());
}
/**
* Deletes notification configuration of a bucket.
*
* Example:{@code
* CompletableFuture future = apiAsyncClient.deleteBucketNotification(
* DeleteBucketNotificationArgs.builder().bucket("my-bucketname").build());
* }
*
* @param args {@link DeleteBucketNotificationArgs} object.
* @return {@link CompletableFuture}<{@link Void}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture deleteBucketNotification(DeleteBucketNotificationArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
return executePutAsync(
args, null, newMultimap("notification", ""), new NotificationConfiguration(), 0)
.thenAccept(response -> response.close());
}
/**
* Gets bucket replication configuration of a bucket.
*
* Example:{@code
* CompletableFuture future =
* apiAsyncClient.getBucketReplication(
* GetBucketReplicationArgs.builder().bucket("my-bucketname").build());
* }
*
* @param args {@link GetBucketReplicationArgs} object.
* @return {@link CompletableFuture}<{@link ReplicationConfiguration}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture getBucketReplication(
GetBucketReplicationArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
return executeGetAsync(args, null, newMultimap("replication", ""))
.exceptionally(
e -> {
Throwable ex = e.getCause();
if (ex instanceof CompletionException) {
ex = ((CompletionException) ex).getCause();
}
if (ex instanceof ExecutionException) {
ex = ((ExecutionException) ex).getCause();
}
if (ex instanceof ErrorResponseException) {
if (((ErrorResponseException) ex)
.errorResponse()
.code()
.equals("ReplicationConfigurationNotFoundError")) {
return null;
}
}
throw new CompletionException(ex);
})
.thenApply(
response -> {
if (response == null) return null;
try {
return Xml.unmarshal(ReplicationConfiguration.class, response.body().charStream());
} catch (XmlParserException e) {
throw new CompletionException(e);
} finally {
response.close();
}
});
}
/**
* Sets bucket replication configuration to a bucket.
*
* Example:{@code
* Map tags = new HashMap<>();
* tags.put("key1", "value1");
* tags.put("key2", "value2");
*
* ReplicationRule rule =
* new ReplicationRule(
* new DeleteMarkerReplication(Status.DISABLED),
* new ReplicationDestination(
* null, null, "REPLACE-WITH-ACTUAL-DESTINATION-BUCKET-ARN", null, null, null, null),
* null,
* new RuleFilter(new AndOperator("TaxDocs", tags)),
* "rule1",
* null,
* 1,
* null,
* Status.ENABLED);
*
* List rules = new LinkedList<>();
* rules.add(rule);
*
* ReplicationConfiguration config =
* new ReplicationConfiguration("REPLACE-WITH-ACTUAL-ROLE", rules);
*
* CompletableFuture future = apiAsyncClient.setBucketReplication(
* SetBucketReplicationArgs.builder().bucket("my-bucketname").config(config).build());
* }
*
* @param args {@link SetBucketReplicationArgs} object.
* @return {@link CompletableFuture}<{@link Void}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture setBucketReplication(SetBucketReplicationArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
return executePutAsync(
args,
(args.objectLockToken() != null)
? newMultimap("x-amz-bucket-object-lock-token", args.objectLockToken())
: null,
newMultimap("replication", ""),
args.config(),
0)
.thenAccept(response -> response.close());
}
/**
* Deletes bucket replication configuration from a bucket.
*
* Example:{@code
* CompletableFuture future = apiAsyncClient.deleteBucketReplication(
* DeleteBucketReplicationArgs.builder().bucket("my-bucketname"));
* }
*
* @param args {@link DeleteBucketReplicationArgs} object.
* @return {@link CompletableFuture}<{@link Void}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture deleteBucketReplication(DeleteBucketReplicationArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
return executeDeleteAsync(args, null, newMultimap("replication", ""))
.thenAccept(response -> response.close());
}
/**
* Listens events of object prefix and suffix of a bucket. The returned closable iterator is
* lazily evaluated hence its required to iterate to get new records and must be used with
* try-with-resource to release underneath network resources.
*
* Example:{@code
* String[] events = {"s3:ObjectCreated:*", "s3:ObjectAccessed:*"};
* try (CloseableIterator> ci =
* apiAsyncClient.listenBucketNotification(
* ListenBucketNotificationArgs.builder()
* .bucket("bucketName")
* .prefix("")
* .suffix("")
* .events(events)
* .build())) {
* while (ci.hasNext()) {
* NotificationRecords records = ci.next().get();
* for (Event event : records.events()) {
* System.out.println("Event " + event.eventType() + " occurred at "
* + event.eventTime() + " for " + event.bucketName() + "/"
* + event.objectName());
* }
* }
* }
* }
*
* @param args {@link ListenBucketNotificationArgs} object.
* @return {@code CloseableIterator>} - Lazy closable iterator
* contains event records.
* @throws ErrorResponseException thrown to indicate S3 service returned an error response.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws InvalidResponseException thrown to indicate S3 service returned invalid or no error
* response.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CloseableIterator> listenBucketNotification(
ListenBucketNotificationArgs args)
throws ErrorResponseException, InsufficientDataException, InternalException,
InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException,
ServerException, XmlParserException {
checkArgs(args);
Multimap queryParams =
newMultimap("prefix", args.prefix(), "suffix", args.suffix());
for (String event : args.events()) {
queryParams.put("events", event);
}
Response response = null;
try {
response = executeGetAsync(args, null, queryParams).get();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throwEncapsulatedException(e);
}
NotificationResultRecords result = new NotificationResultRecords(response);
return result.closeableIterator();
}
/**
* Selects content of an object by SQL expression.
*
* Example:{@code
* String sqlExpression = "select * from S3Object";
* InputSerialization is =
* new InputSerialization(null, false, null, null, FileHeaderInfo.USE, null, null,
* null);
* OutputSerialization os =
* new OutputSerialization(null, null, null, QuoteFields.ASNEEDED, null);
* SelectResponseStream stream =
* apiAsyncClient.selectObjectContent(
* SelectObjectContentArgs.builder()
* .bucket("my-bucketname")
* .object("my-objectname")
* .sqlExpression(sqlExpression)
* .inputSerialization(is)
* .outputSerialization(os)
* .requestProgress(true)
* .build());
*
* byte[] buf = new byte[512];
* int bytesRead = stream.read(buf, 0, buf.length);
* System.out.println(new String(buf, 0, bytesRead, StandardCharsets.UTF_8));
*
* Stats stats = stream.stats();
* System.out.println("bytes scanned: " + stats.bytesScanned());
* System.out.println("bytes processed: " + stats.bytesProcessed());
* System.out.println("bytes returned: " + stats.bytesReturned());
*
* stream.close();
* }
*
* @param args instance of {@link SelectObjectContentArgs}
* @return {@link SelectResponseStream} - Contains filtered records and progress.
* @throws ErrorResponseException thrown to indicate S3 service returned an error response.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws InvalidResponseException thrown to indicate S3 service returned invalid or no error
* response.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public SelectResponseStream selectObjectContent(SelectObjectContentArgs args)
throws ErrorResponseException, InsufficientDataException, InternalException,
InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException,
ServerException, XmlParserException {
checkArgs(args);
args.validateSsec(this.baseUrl);
Response response = null;
try {
response =
executePostAsync(
args,
(args.ssec() != null) ? newMultimap(args.ssec().headers()) : null,
newMultimap("select", "", "select-type", "2"),
new SelectObjectContentRequest(
args.sqlExpression(),
args.requestProgress(),
args.inputSerialization(),
args.outputSerialization(),
args.scanStartRange(),
args.scanEndRange()))
.get();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throwEncapsulatedException(e);
}
return new SelectResponseStream(response.body().byteStream());
}
/**
* Sets encryption configuration of a bucket.
*
* Example:{@code
* CompletableFuture future = apiAsyncClient.putBucketEncryption(
* PutBucketEncryptionArgs.builder().bucket("my-bucketname").config(config).build());
* }
*
* @param args {@link PutBucketEncryptionArgs} object.
* @return {@link CompletableFuture}<{@link Void}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture putBucketEncryption(PutBucketEncryptionArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
return executePutAsync(args, null, newMultimap("encryption", ""), args.config(), 0)
.thenAccept(response -> response.close());
}
/**
* Gets encryption configuration of a bucket.
*
* Example:{@code
* CompletableFuture future =
* apiAsyncClient.getBucketEncryption(
* GetBucketEncryptionArgs.builder().bucket("my-bucketname").build());
* }
*
* @param args {@link GetBucketEncryptionArgs} object.
* @return {@link CompletableFuture}<{@link SseConfiguration}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture getBucketEncryption(GetBucketEncryptionArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
return executeGetAsync(args, null, newMultimap("encryption", ""))
.exceptionally(
e -> {
Throwable ex = e.getCause();
if (ex instanceof CompletionException) {
ex = ((CompletionException) ex).getCause();
}
if (ex instanceof ExecutionException) {
ex = ((ExecutionException) ex).getCause();
}
if (ex instanceof ErrorResponseException) {
if (((ErrorResponseException) ex)
.errorResponse()
.code()
.equals(SERVER_SIDE_ENCRYPTION_CONFIGURATION_NOT_FOUND_ERROR)) {
return null;
}
}
throw new CompletionException(ex);
})
.thenApply(
response -> {
if (response == null) return new SseConfiguration(null);
try {
return Xml.unmarshal(SseConfiguration.class, response.body().charStream());
} catch (XmlParserException e) {
throw new CompletionException(e);
} finally {
response.close();
}
});
}
/**
* Deletes encryption configuration of a bucket.
*
* Example:{@code
* CompletableFuture future = apiAsyncClient.deleteBucketEncryption(
* DeleteBucketEncryptionArgs.builder().bucket("my-bucketname").build());
* }
*
* @param args {@link DeleteBucketEncryptionArgs} object.
* @return {@link CompletableFuture}<{@link Void}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture deleteBucketEncryption(DeleteBucketEncryptionArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
return executeDeleteAsync(args, null, newMultimap("encryption", ""))
.exceptionally(
e -> {
Throwable ex = e.getCause();
if (ex instanceof CompletionException) {
ex = ((CompletionException) ex).getCause();
}
if (ex instanceof ExecutionException) {
ex = ((ExecutionException) ex).getCause();
}
if (ex instanceof ErrorResponseException) {
if (((ErrorResponseException) ex)
.errorResponse()
.code()
.equals(SERVER_SIDE_ENCRYPTION_CONFIGURATION_NOT_FOUND_ERROR)) {
return null;
}
}
throw new CompletionException(ex);
})
.thenAccept(
response -> {
if (response != null) response.close();
});
}
/**
* Gets tags of a bucket.
*
* Example:{@code
* CompletableFuture future =
* apiAsyncClient.getBucketTagging(GetBucketTaggingArgs.builder().bucket("my-bucketname").build());
* }
*
* @param args {@link GetBucketTaggingArgs} object.
* @return {@link CompletableFuture}<{@link Tags}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture getBucketTagging(GetBucketTaggingArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
return executeGetAsync(args, null, newMultimap("tagging", ""))
.exceptionally(
e -> {
Throwable ex = e.getCause();
if (ex instanceof CompletionException) {
ex = ((CompletionException) ex).getCause();
}
if (ex instanceof ExecutionException) {
ex = ((ExecutionException) ex).getCause();
}
if (ex instanceof ErrorResponseException) {
if (((ErrorResponseException) ex).errorResponse().code().equals("NoSuchTagSet")) {
return null;
}
}
throw new CompletionException(ex);
})
.thenApply(
response -> {
if (response == null) return new Tags();
try {
return Xml.unmarshal(Tags.class, response.body().charStream());
} catch (XmlParserException e) {
throw new CompletionException(e);
} finally {
response.close();
}
});
}
/**
* Sets tags to a bucket.
*
* Example:{@code
* Map map = new HashMap<>();
* map.put("Project", "Project One");
* map.put("User", "jsmith");
* CompletableFuture future = apiAsyncClient.putBucketTagging(
* PutBucketTaggingArgs.builder().bucket("my-bucketname").tags(map).build());
* }
*
* @param args {@link PutBucketTaggingArgs} object.
* @return {@link CompletableFuture}<{@link Void}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture putBucketTagging(PutBucketTaggingArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
return executePutAsync(args, null, newMultimap("tagging", ""), args.tags(), 0)
.thenAccept(response -> response.close());
}
/**
* Deletes tags of a bucket.
*
* Example:{@code
* CompletableFuture future = apiAsyncClient.deleteBucketTagging(
* DeleteBucketTaggingArgs.builder().bucket("my-bucketname").build());
* }
*
* @param args {@link DeleteBucketTaggingArgs} object.
* @return {@link CompletableFuture}<{@link Void}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture deleteBucketTagging(DeleteBucketTaggingArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
return executeDeleteAsync(args, null, newMultimap("tagging", ""))
.thenAccept(response -> response.close());
}
/**
* Gets tags of an object.
*
* Example:{@code
* CompletableFuture future =
* apiAsyncClient.getObjectTagging(
* GetObjectTaggingArgs.builder().bucket("my-bucketname").object("my-objectname").build());
* }
*
* @param args {@link GetObjectTaggingArgs} object.
* @return {@link CompletableFuture}<{@link Tags}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture getObjectTagging(GetObjectTaggingArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
Multimap queryParams = newMultimap("tagging", "");
if (args.versionId() != null) queryParams.put("versionId", args.versionId());
return executeGetAsync(args, null, queryParams)
.thenApply(
response -> {
try {
return Xml.unmarshal(Tags.class, response.body().charStream());
} catch (XmlParserException e) {
throw new CompletionException(e);
} finally {
response.close();
}
});
}
/**
* Sets tags to an object.
*
* Example:{@code
* Map map = new HashMap<>();
* map.put("Project", "Project One");
* map.put("User", "jsmith");
* CompletableFuture future = apiAsyncClient.putObjectTagging(
* PutObjectTaggingArgs.builder()
* .bucket("my-bucketname")
* .object("my-objectname")
* .tags((map)
* .build());
* }
*
* @param args {@link PutObjectTaggingArgs} object.
* @return {@link CompletableFuture}<{@link Void}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture putObjectTagging(PutObjectTaggingArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
Multimap queryParams = newMultimap("tagging", "");
if (args.versionId() != null) queryParams.put("versionId", args.versionId());
return executePutAsync(args, null, queryParams, args.tags(), 0)
.thenAccept(response -> response.close());
}
/**
* Deletes tags of an object.
*
* Example:{@code
* CompletableFuture future = apiAsyncClient.deleteObjectTags(
* DeleteObjectTagging.builder().bucket("my-bucketname").object("my-objectname").build());
* }
*
* @param args {@link DeleteObjectTaggingArgs} object.
* @return {@link CompletableFuture}<{@link Void}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture deleteObjectTags(DeleteObjectTaggingArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
Multimap queryParams = newMultimap("tagging", "");
if (args.versionId() != null) queryParams.put("versionId", args.versionId());
return executeDeleteAsync(args, null, queryParams).thenAccept(response -> response.close());
}
/**
* Uploads multiple objects in a single put call. It is done by creating intermediate TAR file
* optionally compressed which is uploaded to S3 service.
*
* Example:{@code
* // Upload snowball objects.
* List objects = new ArrayList();
* objects.add(
* new SnowballObject(
* "my-object-one",
* new ByteArrayInputStream("hello".getBytes(StandardCharsets.UTF_8)),
* 5,
* null));
* objects.add(
* new SnowballObject(
* "my-object-two",
* new ByteArrayInputStream("java".getBytes(StandardCharsets.UTF_8)),
* 4,
* null));
* CompletableFuture future = apiAsyncClient.uploadSnowballObjects(
* UploadSnowballObjectsArgs.builder().bucket("my-bucketname").objects(objects).build());
* }
*
* @param args {@link UploadSnowballObjectsArgs} object.
* @return {@link CompletableFuture}<{@link ObjectWriteResponse}> object.
* @throws InsufficientDataException thrown to indicate not enough data available in InputStream.
* @throws InternalException thrown to indicate internal library error.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on S3 operation.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws XmlParserException thrown to indicate XML parsing error.
*/
public CompletableFuture uploadSnowballObjects(
UploadSnowballObjectsArgs args)
throws InsufficientDataException, InternalException, InvalidKeyException, IOException,
NoSuchAlgorithmException, XmlParserException {
checkArgs(args);
return CompletableFuture.supplyAsync(
() -> {
FileOutputStream fos = null;
BufferedOutputStream bos = null;
SnappyFramedOutputStream sos = null;
ByteArrayOutputStream baos = null;
TarArchiveOutputStream tarOutputStream = null;
try {
OutputStream os = null;
if (args.stagingFilename() != null) {
fos = new FileOutputStream(args.stagingFilename());
bos = new BufferedOutputStream(fos);
os = bos;
} else {
baos = new ByteArrayOutputStream();
os = baos;
}
if (args.compression()) {
sos = new SnappyFramedOutputStream(os);
os = sos;
}
tarOutputStream = new TarArchiveOutputStream(os);
tarOutputStream.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
for (SnowballObject object : args.objects()) {
if (object.filename() != null) {
Path filePath = Paths.get(object.filename());
TarArchiveEntry entry = new TarArchiveEntry(filePath.toFile(), object.name());
tarOutputStream.putArchiveEntry(entry);
Files.copy(filePath, tarOutputStream);
} else {
TarArchiveEntry entry = new TarArchiveEntry(object.name());
if (object.modificationTime() != null) {
entry.setModTime(Date.from(object.modificationTime().toInstant()));
}
entry.setSize(object.size());
tarOutputStream.putArchiveEntry(entry);
ByteStreams.copy(object.stream(), tarOutputStream);
}
tarOutputStream.closeArchiveEntry();
}
tarOutputStream.finish();
} catch (IOException e) {
throw new CompletionException(e);
} finally {
try {
if (tarOutputStream != null) tarOutputStream.flush();
if (sos != null) sos.flush();
if (bos != null) bos.flush();
if (fos != null) fos.flush();
if (tarOutputStream != null) tarOutputStream.close();
if (sos != null) sos.close();
if (bos != null) bos.close();
if (fos != null) fos.close();
} catch (IOException e) {
throw new CompletionException(e);
}
}
return baos;
})
.thenCompose(
baos -> {
Multimap headers = newMultimap(args.extraHeaders());
headers.putAll(args.genHeaders());
headers.put("X-Amz-Meta-Snowball-Auto-Extract", "true");
if (args.stagingFilename() == null) {
byte[] data = baos.toByteArray();
try {
return putObjectAsync(
args.bucket(),
args.region(),
args.object(),
data,
data.length,
headers,
args.extraQueryParams());
} catch (InsufficientDataException
| InternalException
| InvalidKeyException
| IOException
| NoSuchAlgorithmException
| XmlParserException e) {
throw new CompletionException(e);
}
}
long length = Paths.get(args.stagingFilename()).toFile().length();
if (length > ObjectWriteArgs.MAX_OBJECT_SIZE) {
throw new IllegalArgumentException(
"tarball size " + length + " is more than maximum allowed 5TiB");
}
try (RandomAccessFile file = new RandomAccessFile(args.stagingFilename(), "r")) {
return putObjectAsync(
args.bucket(),
args.region(),
args.object(),
file,
length,
headers,
args.extraQueryParams());
} catch (InsufficientDataException
| InternalException
| InvalidKeyException
| IOException
| NoSuchAlgorithmException
| XmlParserException e) {
throw new CompletionException(e);
}
});
}
public static Builder builder() {
return new Builder();
}
/** Argument builder of {@link ApiClient}. */
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 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 ApiAsyncClient 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.httpClient =
HttpUtils.newDefaultHttpClient(
DEFAULT_CONNECTION_TIMEOUT, DEFAULT_CONNECTION_TIMEOUT, DEFAULT_CONNECTION_TIMEOUT);
}
return new ApiAsyncClient(
baseUrl,
awsS3Prefix,
awsDomainSuffix,
awsDualstack,
useVirtualStyle,
region,
provider,
httpClient);
}
}
}