package thredds.crawlabledataset.s3;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.*;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.util.Objects;
/**
* A basic implementation of {@link ThreddsS3Client}.
*
* @author cwardgar
* @since 2015/08/22
*/
public class ThreddsS3ClientImpl implements ThreddsS3Client {
private static final Logger logger = LoggerFactory.getLogger(ThreddsS3ClientImpl.class);
private final AmazonS3Client s3Client;
public ThreddsS3ClientImpl() {
// Use HTTP; it's much faster.
this.s3Client = new AmazonS3Client();
this.s3Client.setEndpoint("http://s3.amazonaws.com");
}
public ThreddsS3ClientImpl(AmazonS3Client s3Client) {
this.s3Client = s3Client;
}
@Override
public ObjectMetadata getObjectMetadata(S3URI s3uri) {
try {
ObjectMetadata metadata = s3Client.getObjectMetadata(s3uri.getBucket(), s3uri.getKey());
logger.info(String.format("Downloaded S3 metadata '%s'", s3uri));
return metadata;
} catch (IllegalArgumentException e) { // Thrown by getObjectMetadata() when key == null.
logger.debug(e.getMessage());
return null;
} catch (AmazonServiceException e) {
if (e.getStatusCode() == HttpStatus.SC_NOT_FOUND) {
logger.debug(String.format(
"There is no S3 bucket '%s' that has key '%s'.", s3uri.getBucket(), s3uri.getKey()));
return null;
} else {
throw e;
}
}
}
@Override
public ObjectListing listObjects(S3URI s3uri) {
ListObjectsRequest listObjectsRequest =
new ListObjectsRequest().withBucketName(s3uri.getBucket()).withDelimiter(S3URI.S3_DELIMITER);
if (s3uri.getKey() != null) {
listObjectsRequest.setPrefix(s3uri.getKeyWithTrailingDelimiter());
}
try {
ObjectListing objectListing = s3Client.listObjects(listObjectsRequest);
logger.info(String.format("Downloaded S3 listing '%s'", s3uri));
// On S3 it is possible for a prefix to be a valid object (unlike a
// filesystem where a node is either a file OR a directory). We
// exclude self here so that the "directory" isn't treated as a file
// by some of the caching mechanism.
S3ObjectSummary self = null;
for (S3ObjectSummary objectSummary: objectListing.getObjectSummaries()) {
if (Objects.equals(objectSummary.getKey(), s3uri.getKeyWithTrailingDelimiter())) {
self = objectSummary;
break;
}
}
objectListing.getObjectSummaries().remove(self);
if (objectListing.getObjectSummaries().isEmpty() && objectListing.getCommonPrefixes().isEmpty()) {
// There are no empty directories in a S3 hierarchy.
logger.debug(String.format("In bucket '%s', the key '%s' does not denote an existing virtual directory.",
s3uri.getBucket(), s3uri.getKey()));
return null;
} else {
return objectListing;
}
} catch (AmazonServiceException e) {
if (e.getStatusCode() == HttpStatus.SC_NOT_FOUND) {
logger.debug(String.format("No S3 bucket named '%s' exists.", s3uri.getBucket()));
return null;
} else {
throw e;
}
}
}
@Override
public File saveObjectToFile(S3URI s3uri, File file) throws IOException {
try {
s3Client.getObject(new GetObjectRequest(s3uri.getBucket(), s3uri.getKey()), file);
logger.info(String.format("Downloaded S3 object '%s' to '%s'", s3uri, file));
file.deleteOnExit();
return file;
} catch (IllegalArgumentException e) { // Thrown by getObject() when key == null.
logger.debug(e.getMessage());
return null;
} catch (AmazonServiceException e) {
if (e.getStatusCode() == HttpStatus.SC_NOT_FOUND) {
logger.debug(String.format(
"There is no S3 bucket '%s' that has key '%s'.", s3uri.getBucket(), s3uri.getKey()));
return null;
} else {
throw e;
}
}
}
}