package jeffaschenk.commons.system.external.images.aws; import com.amazonaws.AmazonClientException; import com.amazonaws.ClientConfiguration; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.model.*; import jeffaschenk.commons.environment.SpringAccessor; import jeffaschenk.commons.environment.SystemEnvironmentBean; import jeffaschenk.commons.types.EnvironmentType; import jeffaschenk.commons.types.ImageServiceType; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.Calendar; import java.util.Date; import java.util.Iterator; import java.util.List; /** * AWS S3 Bucket Service Implementation to provide storage for * Application Images. */ public class S3ServiceImpl implements S3Service { private final static Log logger = LogFactory.getLog(S3ServiceImpl.class); /** * Common S3 Client Object */ private boolean deferredClientConnection = false; private AmazonS3Client s3; /** * AWS S3 Bucket Name for Auction an Item Images. */ private String primaryBucket; /** * Calculated Expiration of Images */ private Date imageExpiration; /** * AWS S3 Bucket for all User Avatar Images, to provide some separation between * Auction and Item Images and User Images. */ private String avatarBucket; /** * Calculated Expiration of Images */ private Date avatarExpiration; /** * Default Constructor, Injected via Spring Configuration * to use AWS Services. * * @param accessKey * @param secretKey * @param bucket */ public S3ServiceImpl(String runningImageService, String accessKey, String secretKey, String bucket, String avatarBucket, int connectionTimeOut, int maxRetries) { // If we are running in the ESB, do not start this Service. SystemEnvironmentBean systemEnvironment = SpringAccessor.getSystemEnvironmentBean(); if (systemEnvironment.runningEnvironmentType() == EnvironmentType.ESB) { this.deferredClientConnection = true; this.s3 = null; logger.info("ESB Environment Detected, Amazon Web Services (AWS) S3 Bucket Service Disabled."); } else { // We are not in ESB so, boot the service. if (runningImageService.equalsIgnoreCase(ImageServiceType.S3Service.toString())) { logger.info("Initializing Amazon Web Services (AWS) S3 Bucket Service Implementation."); this.primaryBucket = bucket; this.avatarBucket = avatarBucket; // **************************************** // Calculate the Expiration of Objects Calendar calendar = Calendar.getInstance(); calendar.set(2020, 1, 1); imageExpiration = calendar.getTime(); // **************************************** // Calculate the Expiration of Objects avatarExpiration = calendar.getTime(); // ********************************** // Obtain AWS S3 Client. ClientConfiguration clientConfiguration = new ClientConfiguration(); clientConfiguration.setConnectionTimeout(connectionTimeOut); clientConfiguration.setMaxErrorRetry(maxRetries); s3 = new AmazonS3Client(new BasicAWSCredentials(accessKey, secretKey), clientConfiguration); // Indicate our Bucket Usage logger.info("AWS S3 Primary Avatar Bucket:[" + avatarBucket + "]"); logger.info("AWS S3 Primary Image Bucket:[" + bucket + "]"); // Indicate Configuration logger.info("AWS S3 Connection Time Out:[" + clientConfiguration.getConnectionTimeout() + "], Max Retries:[" + clientConfiguration.getMaxErrorRetry() + "]"); // ********************************** // Attempt to // List Available Buckets and ensure // they have correct Read permissions. try { List<Bucket> availableBuckets = s3.listBuckets(); Iterator<Bucket> itr = availableBuckets.iterator(); while (itr.hasNext()) { Bucket aBucket = itr.next(); logger.info(" + (AWS)S3 Bucket available:[" + aBucket.getName() + "], setting Read Permissions."); AccessControlList accessControlList = s3.getBucketAcl(aBucket.getName()); accessControlList.grantPermission(GroupGrantee.AllUsers, Permission.Read); s3.setBucketAcl(aBucket.getName(), accessControlList); } } catch (AmazonClientException ace) { this.deferredClientConnection = true; logger.error("Attempting Access to (AWS)S3 has failed:[" + ace.getMessage() + "], Cause:[" + ace.getCause() + "]."); } // Done, Ready. if (this.deferredClientConnection) { logger.info("Amazon Web Services (AWS) S3 Bucket Service Implementation Initialization Deferred."); } else { logger.info("Amazon Web Services (AWS) S3 Bucket Service Implementation Successfully Initialized."); } } } } @Override public boolean isDeferredClientConnection() { return deferredClientConnection; } @Override public ImageServiceType getImageServiceType() { return ImageServiceType.S3Service; } @Override public URL generateResourceURL(String key) { if (s3 == null) { return null; } try { return s3.generatePresignedUrl(primaryBucket, key, imageExpiration); } catch (AmazonClientException ace) { this.deferredClientConnection = true; logger.error("Attempting Access to (AWS)S3 has failed:[" + ace.getMessage() + "], Cause:[" + ace.getCause() + "]."); } throw new RuntimeException("Unable to Obtain Pre-signed Url for Image."); } @Override public URL generateAvatarURL(String key) { if (s3 == null) { return null; } try { return s3.generatePresignedUrl(avatarBucket, key, avatarExpiration); } catch (AmazonClientException ace) { this.deferredClientConnection = true; logger.error("Attempting Access to (AWS)S3 has failed:[" + ace.getMessage() + "], Cause:[" + ace.getCause() + "]."); } throw new RuntimeException("Unable to Obtain Pre-signed Url for Avatar."); } @Override public String[] createImage(String sid, InputStream imageContent, String contentType, long size, boolean createThumbnail) { if (s3 == null) { return new String[2]; } try { return saveContent(this.primaryBucket, sid, imageContent, contentType, size, createThumbnail); } catch (AmazonClientException ace) { this.deferredClientConnection = true; logger.error("Attempting Access to (AWS)S3 has failed:[" + ace.getMessage() + "], Cause:[" + ace.getCause() + "]."); } throw new RuntimeException("Unable to Create Image."); } @Override public void deleteImage(String filename) { if (s3 == null) { return; } try { s3.deleteObject(primaryBucket, filename); } catch (AmazonClientException ace) { this.deferredClientConnection = true; logger.error("Attempting Access to (AWS)S3 has failed:[" + ace.getMessage() + "], Cause:[" + ace.getCause() + "]."); } } @Override public String[] createAvatar(String sid, InputStream imageContent, String contentType, long size, boolean createThumbnail) { if (s3 == null) { return new String[2]; } try { return saveContent(this.avatarBucket, sid, imageContent, contentType, size, createThumbnail); } catch (AmazonClientException ace) { this.deferredClientConnection = true; logger.error("Attempting Access to (AWS)S3 has failed:[" + ace.getMessage() + "], Cause:[" + ace.getCause() + "]."); } throw new RuntimeException("Unable to Create Avatar."); } @Override public void deleteAvatar(String filename) { if (s3 == null) { return; } try { s3.deleteObject(primaryBucket, filename); } catch (AmazonClientException ace) { this.deferredClientConnection = true; logger.error("Attempting Access to (AWS)S3 has failed:[" + ace.getMessage() + "], Cause:[" + ace.getCause() + "]."); } } /** * Private Helper Method to Save Avatar or Images to a specific Bucket. * * @param storageBucket * @param sid * @param imageContent * @param contentType * @param size * @param createThumbnail * @return String[] */ private String[] saveContent(String storageBucket, String sid, InputStream imageContent, String contentType, long size, boolean createThumbnail) throws AmazonClientException { String[] fileNames = new String[2]; ObjectMetadata objectMetadata = new ObjectMetadata(); objectMetadata.setContentLength(size); objectMetadata.setContentType(contentType); objectMetadata.setLastModified(new Date()); String filename = sid + ".jpg"; // Save the Content try { ByteArrayOutputStream tempBuffer = new ByteArrayOutputStream(imageContent.available()); IOUtils.copy(imageContent, tempBuffer); byte[] image = tempBuffer.toByteArray(); // Store Image s3.putObject(storageBucket, filename, new ByteArrayInputStream(image), objectMetadata); fileNames[0] = filename; // Grant Access Rights to All users. AccessControlList accessControlList = s3.getObjectAcl(storageBucket, fileNames[0]); accessControlList.grantPermission(GroupGrantee.AllUsers, Permission.Read); s3.setObjectAcl(storageBucket, fileNames[0], accessControlList); // Thumbnail to be saved? if (createThumbnail) { /** // Add thumbnail byte[] thumbnail = ImageUtils.createThumbnailImage(new ByteArrayInputStream(image), null); ObjectMetadata thumbnailMetadata = new ObjectMetadata(); thumbnailMetadata.setContentLength(thumbnail.length); thumbnailMetadata.setContentType(contentType); thumbnailMetadata.setLastModified(new Date()); // Store Thumbnail s3.putObject(storageBucket, sid + "t.jpg", new ByteArrayInputStream(thumbnail), thumbnailMetadata); fileNames[1] = sid + "t.jpg"; // Grant Access Rights to All users. accessControlList = s3.getObjectAcl(storageBucket, fileNames[1]); accessControlList.grantPermission(GroupGrantee.AllUsers, Permission.Read); s3.setObjectAcl(storageBucket, fileNames[1], accessControlList); **/ } } catch (IOException e) { throw new RuntimeException(e); } return fileNames; } }