package com.example.services.impl; import com.example.utils.SQSUtils; import com.example.config.Config; import com.example.services.FileManager; import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.model.*; import com.amazonaws.services.s3.transfer.TransferManager; import com.amazonaws.services.sqs.AmazonSQSClient; import java.io.ByteArrayInputStream; import java.io.File; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; public class S3FileManager implements FileManager { private final AmazonS3Client client; private final Config config; private final String bucket; private final static Logger logger = Logger.getLogger(S3FileManager.class.getName()); // Keeps track of files are that just downloaded, so we don't enter a feedback loop private volatile List<File> justDownloaded = new ArrayList<File>(); public S3FileManager(Config config) { this.config = config; client = new AmazonS3Client(config.getAWSCredentials()); bucket = config.getBucketName(); populateLocalRepoIfFirstTime(); } protected void populateLocalRepoIfFirstTime() { if (isFirstTime()) { syncEntireBucket(); } else { logger.info("Queue already present"); } } protected void syncEntireBucket() { logger.info("Syncing entire bucket..."); TransferManager manager = new TransferManager(config.getAWSCredentials()); manager.downloadDirectory(config.getBucketName(), null, config.getBaseDir().toFile()); } /** * Loops through every queue and checks if queue is already present. * If present, it means this is not the first time. * @return is first time */ protected boolean isFirstTime() { boolean firstTime = true; AmazonSQSClient sqsClient = new AmazonSQSClient(config.getAWSCredentials()); for(String queueUrl: sqsClient.listQueues().getQueueUrls()) { queueUrl = SQSUtils.getQueueNameFromQueueUrl(queueUrl); if (queueUrl.equals(config.getQueueName())) { firstTime = false; break; } } return firstTime; } /** * Checks if a file should be downloaded. If a file has just been downloaded, the DirectoryWatcher * will still generate an event when the file get created/modified. In that case, we should not send an * update, else it should be downloaded * @param file the file to check * @return whether to download the file */ public boolean shouldDownload(File file) { if (justDownloaded.contains(file)) { logger.finest("Already downloaded - " + config.getQueueName() + " for " + file.getAbsolutePath()); justDownloaded.remove(file); return false; } return true; } @Override public boolean upload(File file) { PutObjectRequest request; if (file.isDirectory()) { ObjectMetadata metadata = new ObjectMetadata(); metadata.setContentLength(0); InputStream emptyContent = new ByteArrayInputStream(new byte[0]); request = new PutObjectRequest(bucket, getFilePath(file), emptyContent, metadata); } else { request = new PutObjectRequest(bucket, getFilePath(file), file); } return uploadToS3(request); } protected boolean uploadToS3(PutObjectRequest putObjectRequest) { boolean uploadSuccessful = false; try { client.putObject(putObjectRequest); logger.fine("Uploading - " + config.getQueueName() + " for " + putObjectRequest.getKey()); uploadSuccessful = true; } catch (AmazonS3Exception e) { logger.warning("Unable to upload file " + e); } return uploadSuccessful; } @Override public boolean download(File file) { if (!file.isDirectory()) { GetObjectRequest request = new GetObjectRequest(bucket, getFilePath(file)); boolean successful = downloadFromS3(request, file); if (successful) { justDownloaded.add(file); return true; } else { return false; } } else { return false; } } protected boolean downloadFromS3(GetObjectRequest getObjectRequest, File file) { boolean downloadSuccessful = false; try { client.getObject(getObjectRequest, file); logger.info("Downloading - " + config.getQueueName() + " for " + getObjectRequest.getKey()); downloadSuccessful = true; } catch (AmazonS3Exception e) { logger.warning(" Unable to download file, deleted maybe? " + e); } return downloadSuccessful; } @Override public boolean delete(File file) { DeleteObjectRequest request = new DeleteObjectRequest(bucket, getFilePath(file)); return deleteInS3(request); } @Override public boolean deleteLocalFile(File file) { return file.delete(); } @Override public boolean createLocalDirectory(File file) { justDownloaded.add(file); return file.mkdir(); } protected boolean deleteInS3(DeleteObjectRequest deleteObjectRequest) { client.deleteObject(deleteObjectRequest); return true; } private String getFilePath(File file) { String path = config.getBaseDir().relativize(file.toPath()).toString(); if (file.isDirectory()) { // The trailing "/" is kinda important for S3 to // know that it is a directory path += "/"; } return path; } }