001/**
002 * Copyright 2005-2017 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.kew.notes.service.impl;
017
018import java.io.File;
019import java.net.URLEncoder;
020import java.util.UUID;
021
022import org.apache.commons.lang3.StringUtils;
023import org.kuali.rice.kew.notes.Attachment;
024import org.kuali.rice.kew.notes.service.AttachmentService;
025import org.springframework.beans.factory.InitializingBean;
026import org.springframework.beans.factory.annotation.Autowired;
027import org.springframework.beans.factory.annotation.Required;
028import org.springframework.core.io.Resource;
029import org.springframework.core.io.ResourceLoader;
030
031import com.amazonaws.services.s3.AmazonS3;
032import com.amazonaws.services.s3.model.DeleteObjectRequest;
033import com.amazonaws.services.s3.model.ObjectMetadata;
034import com.amazonaws.services.s3.transfer.TransferManager;
035import com.amazonaws.services.s3.transfer.Upload;
036
037/**
038 * An {@link AttachmentService} implementation which utilizes AWS Simple Storage Service.
039 * 
040 * <p>Must inject a ResourceLoader that is capable of resolving "s3://" urls as well as the name of the bucket this
041 * service should use. Also an instance of the AmazonS3 client api should be injected.
042 * 
043 * The generated id of the attachment will be used as the object key is S3, so the bucket should exist only for storing
044 * the attachments managed by this service.
045 * 
046 */
047public class AmazonS3AttachmentServiceImpl implements AttachmentService, InitializingBean {
048
049        private ResourceLoader resourceLoader;
050        private String bucketName;
051        private String folderName;
052        private AmazonS3 amazonS3;
053        
054        @Override
055        public void afterPropertiesSet() {
056                if (StringUtils.isBlank(folderName)) {
057                        throw new IllegalStateException("S3 attachment service must be configured with a non-blank folder name");
058                }
059                if (StringUtils.isBlank(bucketName)) {
060                        throw new IllegalStateException("S3 attachment service must be configured with a non-blank bucket name");
061                }               
062        }
063
064        @Override
065        public void persistAttachedFileAndSetAttachmentBusinessObjectValue(Attachment attachment) throws Exception {
066                if (attachment.getFileLoc() == null) {
067                        String s3Url = generateS3Url(attachment);
068                        attachment.setFileLoc(s3Url);
069                }
070                TransferManager manager = new TransferManager(this.amazonS3);
071                ObjectMetadata metadata = new ObjectMetadata();
072                if (attachment.getMimeType() != null) {
073                        metadata.setContentType(attachment.getMimeType());
074                }
075                if (attachment.getFileName() != null) {
076                        metadata.setContentDisposition("attachment; filename=" + URLEncoder.encode(attachment.getFileName(), "UTF-8"));
077                }
078                Upload upload = manager.upload(this.bucketName, parseObjectKey(attachment.getFileLoc()), attachment.getAttachedObject(), metadata);
079                upload.waitForCompletion();
080        }
081
082        @Override
083        public File findAttachedFile(Attachment attachment) throws Exception {
084                throw new UnsupportedOperationException("S3 Attachment Service implementation cannot provide a file, please you \"findAttachedResource\" instead.");
085        }
086        
087        @Override
088        public Resource findAttachedResource(Attachment attachment) {
089                if (attachment == null) {
090                        throw new IllegalArgumentException("Given attachment was null");
091                }
092                if (StringUtils.isBlank(attachment.getFileLoc())) {
093                        throw new IllegalArgumentException("Given attachment has an empty file location");
094                }
095                return this.resourceLoader.getResource(attachment.getFileLoc());                
096        }
097
098        @Override
099        public void deleteAttachedFile(Attachment attachment) throws Exception {
100                amazonS3.deleteObject(new DeleteObjectRequest(this.bucketName, parseObjectKey(attachment.getFileLoc())));
101        }
102                
103        private String generateS3Url(Attachment attachment) {
104                return generateS3Prefix() + folderName + "/" + UUID.randomUUID();               
105        }
106        
107        private String generateS3Prefix() {
108                return "s3://" + this.bucketName + "/";
109        }
110        
111        private String parseObjectKey(String s3Url) {
112                String prefix = generateS3Prefix();
113                String objectKey = s3Url.substring(prefix.length());
114                return objectKey;
115        }
116        
117        @Required
118        @Autowired
119        public void setResourceLoader(ResourceLoader resourceLoader) {
120                this.resourceLoader = resourceLoader;
121        }
122
123        @Required
124        public void setFolderName(String folderName) {
125                this.folderName = folderName;
126        }
127
128        @Required
129        public void setBucketName(String bucketName) {
130                this.bucketName = bucketName;
131        }
132
133        @Required
134        @Autowired
135        public void setAmazonS3(AmazonS3 amazonS3) {
136                this.amazonS3 = amazonS3;
137        }
138
139}