package core.aws.client; import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.regions.Regions; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3ClientBuilder; import com.amazonaws.services.s3.model.AmazonS3Exception; import com.amazonaws.services.s3.model.Bucket; import com.amazonaws.services.s3.model.BucketTaggingConfiguration; import com.amazonaws.services.s3.model.DeleteObjectsRequest; import com.amazonaws.services.s3.model.ListObjectsRequest; import com.amazonaws.services.s3.model.ObjectListing; import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.PutObjectRequest; import com.amazonaws.services.s3.model.S3Object; import com.amazonaws.services.s3.model.S3ObjectSummary; import com.amazonaws.services.s3.model.StorageClass; import com.amazonaws.services.s3.model.TagSet; import com.amazonaws.util.BinaryUtils; import com.amazonaws.util.Md5Utils; import core.aws.util.Asserts; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; /** * @author neo */ public class S3 { public final AmazonS3 s3; private final Logger logger = LoggerFactory.getLogger(getClass()); public S3(AWSCredentialsProvider credentials, Regions region) { s3 = AmazonS3ClientBuilder.standard().withRegion(region).withCredentials(credentials).build(); } public void createFolder(String bucket, String folder) { Asserts.isTrue(folder.startsWith("/"), "s3 key can't start with /, folder={}", folder); logger.info("create folder, bucket={}, folder={}", bucket, folder); InputStream input = new ByteArrayInputStream(new byte[0]); ObjectMetadata metadata = new ObjectMetadata(); metadata.setContentLength(0); s3.putObject(new PutObjectRequest(bucket, folder, input, metadata).withStorageClass(StorageClass.ReducedRedundancy)); } public void putObject(String bucket, String key, String content) { Asserts.isFalse(key.startsWith("/"), "s3 key can't start with /, key={}", key); byte[] bytes = content.getBytes(Charset.forName("UTF-8")); String etag = etag(bytes); if (etagMatches(bucket, key, etag)) return; logger.info("put string content, bucket={}, key={}, contentLength={}", bucket, key, bytes.length); ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes); ObjectMetadata objectMetadata = new ObjectMetadata(); objectMetadata.setContentLength(bytes.length); s3.putObject(new PutObjectRequest(bucket, key, inputStream, objectMetadata).withStorageClass(StorageClass.ReducedRedundancy)); } public void putObject(String bucket, String key, File file) { Asserts.isFalse(key.startsWith("/"), "s3 key can't start with /, key={}", key); String etag = etag(file); if (etagMatches(bucket, key, etag)) return; logger.info("put object, bucket={}, key={}, file={}", bucket, key, file.getAbsoluteFile()); s3.putObject(new PutObjectRequest(bucket, key, file).withStorageClass(StorageClass.ReducedRedundancy)); } public void deleteAll(String bucket) { logger.info("delete all from bucket, bucket={}", bucket); ObjectListing listing = s3.listObjects(new ListObjectsRequest().withBucketName(bucket)); while (listing != null) { List<DeleteObjectsRequest.KeyVersion> keys = new ArrayList<>(listing.getObjectSummaries().size()); for (S3ObjectSummary summary : listing.getObjectSummaries()) { String key = summary.getKey(); logger.info("add key to deletion batch, key={}", key); keys.add(new DeleteObjectsRequest.KeyVersion(key)); } if (!keys.isEmpty()) { logger.info("delete key batch"); s3.deleteObjects(new DeleteObjectsRequest(bucket).withKeys(keys)); } if (!listing.isTruncated()) return; listing = s3.listNextBatchOfObjects(listing); } } public void putFolder(String bucket, File folder, String prefix) { logger.info("put folder, bucket={}, folder={}, keyPrefix={}", bucket, folder, prefix); URI rootURI = folder.getParentFile().toURI(); putFolder(bucket, folder, rootURI, prefix); } private void putFolder(String bucket, File folder, URI rootURI, String prefix) { File[] childFiles = folder.listFiles(); if (childFiles != null) for (File childFile : childFiles) { if (childFile.isDirectory()) { putFolder(bucket, childFile, rootURI, prefix); } else { String key = prefix + rootURI.relativize(childFile.toURI()).toString(); putObject(bucket, key, childFile); } } } // use same code of aws s3 client to calculate etag, refer to AmazonS3Client.putObject() private String etag(File file) { try { FileInputStream fileInputStream = new FileInputStream(file); byte[] md5Hash = Md5Utils.computeMD5Hash(fileInputStream); return BinaryUtils.toHex(md5Hash); } catch (IOException e) { throw new IllegalStateException(e); } } private String etag(byte[] bytes) { byte[] md5Hash = Md5Utils.computeMD5Hash(bytes); return BinaryUtils.toHex(md5Hash); } private boolean etagMatches(String bucket, String key, String etag) { try { S3Object object = s3.getObject(bucket, key); if (object != null) { String existingETag = object.getObjectMetadata().getETag(); if (etag.equals(existingETag)) { logger.info("etag matches, skip uploading, bucket={}, key={}", bucket, key); return true; } } } catch (AmazonS3Exception e) { if (!"NoSuchKey".equals(e.getErrorCode())) { throw e; } } return false; } public List<Bucket> listAllBuckets() { logger.info("list all s3 buckets"); return s3.listBuckets(); } public Bucket createBucket(String bucketName) { logger.info("create s3 bucket, name={}", bucketName); return s3.createBucket(bucketName); } public void deleteBucket(String bucketName) { logger.info("delete s3 bucket, name={}", bucketName); s3.deleteBucket(bucketName); } public void createTags(String bucketName, TagSet tags) { logger.info("tag s3 bucket, bucketName={}", bucketName); s3.setBucketTaggingConfiguration(bucketName, new BucketTaggingConfiguration().withTagSets(tags)); } }