Skip to content

Commit d331398

Browse files
author
Shashank Agrawal
authored
Merge pull request #4 from wizpanda/amazon-s3
Amazon S3 implementation
2 parents d7e26ec + d8dfd0c commit d331398

File tree

15 files changed

+429
-37
lines changed

15 files changed

+429
-37
lines changed

build.gradle

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ buildscript {
1111
}
1212
}
1313

14-
version "0.1"
15-
group "multi.file.upload"
14+
version "0.0.3"
15+
group "com.wizpanda.plugins"
1616

1717
apply plugin:"eclipse"
1818
apply plugin:"idea"
@@ -40,6 +40,8 @@ dependencies {
4040
compile "org.springframework.boot:spring-boot-starter-logging"
4141
compile "org.springframework.boot:spring-boot-autoconfigure"
4242
compile "org.grails:grails-core"
43+
compile group: "org.apache.jclouds", name: "jclouds-core", version: "1.9.2"
44+
compile group: "org.apache.jclouds", name: "jclouds-allblobstore", version: "1.9.2"
4345
console "org.grails:grails-console"
4446
profile "org.grails.profiles:plugin"
4547
provided "org.grails:grails-plugin-services"

grails-app/controllers/com/wizpanda/file/UploadController.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class UploadController {
3535
* }
3636
* </pre>
3737
*
38-
* Note: By default this endpoint is blocked to allow upload. You need to set following to your config in order
38+
* Note: By default this endpoint is blocked to allow upload. You need to set following to your service in order
3939
* to allow uploading files using this endpoint:
4040
*
4141
* <pre>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.wizpanda.file
2+
3+
class StoredFile {
4+
5+
String originalName
6+
String name
7+
String url
8+
String groupName
9+
Long size
10+
Date uploadedOn = new Date()
11+
Map meta = [:]
12+
}
Lines changed: 32 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,46 @@
11
package com.wizpanda.file
22

3-
import org.springframework.context.MessageSource
4-
import org.springframework.web.multipart.commons.CommonsMultipartFile
3+
import com.wizpanda.file.exception.FileUploadException
4+
import com.wizpanda.file.exception.InvalidFileGroupException
5+
import com.wizpanda.file.service.UploaderService
6+
import org.springframework.web.multipart.MultipartFile
57

6-
import java.nio.file.Files
8+
import javax.annotation.PostConstruct
79

810
class FileUploadService {
911

10-
MessageSource messageSource
11-
12-
/**
13-
* Save the uploaded multipart file in the temporary directory of the local server which will be cleaned
14-
* automatically by the system itself.
15-
*
16-
* @param multipartFile
17-
* @return Saved file in a temporary location
18-
* @throws FileNotFoundException
19-
* @throws IOException
20-
*/
21-
File saveTemporarily(CommonsMultipartFile multipartFile) throws FileNotFoundException, IOException {
22-
if (!multipartFile || multipartFile.isEmpty()) {
23-
log.debug "Received file is either empty or does not exists"
24-
25-
throw new FileNotFoundException(messageSource.getMessage("kernel.uploaded.file.empty", null, null))
26-
}
12+
private static Map<String, UploaderService> services = [:]
13+
14+
StoredFile save(MultipartFile multipartFile, String groupName) throws FileUploadException {
15+
// TODO Add check for validating group name
16+
17+
return services.get(groupName).instance().save(multipartFile)
18+
}
19+
20+
@PostConstruct
21+
void verifyConfig() {
22+
//log.debug "Verifying all service"
23+
println "Verifying all service"
2724

28-
try {
29-
String originalFilename = multipartFile.getOriginalFilename()
30-
File temporaryDirectory = Files.createTempDirectory(null).toFile()
25+
ConfigHelper.allGroups.each { Map.Entry groupConfigEntry ->
26+
String groupName = groupConfigEntry.key.toString()
27+
Map groupConfigValue = groupConfigEntry.value
3128

32-
log.debug "Uploaded file [$originalFilename] will be saved in [${temporaryDirectory.absolutePath}]"
29+
if (!groupConfigValue.service) {
30+
throw new InvalidFileGroupException("The service API missing for [${groupName}]")
31+
}
3332

34-
File temporaryFile = new File(temporaryDirectory, originalFilename)
35-
multipartFile.transferTo(temporaryFile)
33+
Class<? extends UploaderService> serviceClass = groupConfigValue.service
3634

37-
return temporaryFile
38-
} catch (IllegalStateException e) {
39-
log.error "Problem saving file", e
40-
throw new FileNotFoundException(e.message)
35+
UploaderService service
36+
try {
37+
service = serviceClass.newInstance(groupName, groupConfigValue)
38+
} catch (IllegalAccessException | InstantiationException | RuntimeException e) {
39+
log.error "Error creating uploader service object", e
40+
throw new InvalidFileGroupException("Error while creating the uploader service object");
41+
}
4142

42-
} catch (IOException e) {
43-
log.error "Exception saving file", e
44-
throw e
43+
services[service.groupName] = service
4544
}
4645
}
4746
}

src/main/groovy/multi/file/upload/MultiFileUploadGrailsPlugin.groovy renamed to src/main/groovy/com/wizpanda/MultiFileUploadGrailsPlugin.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package multi.file.upload
1+
package com.wizpanda
22

33
import grails.plugins.Plugin
44

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.wizpanda.file
2+
3+
import com.wizpanda.file.exception.FileUploadException
4+
import grails.util.Holders
5+
6+
class ConfigHelper {
7+
8+
private static final String BASE_CONFIG = "fileUpload"
9+
10+
static Object getFlatConfig(String suffix) {
11+
return Holders.getFlatConfig()[BASE_CONFIG + "." + suffix]
12+
}
13+
14+
static ConfigObject getAllGroups() {
15+
return Holders.getConfig()[BASE_CONFIG + ".groups"]
16+
}
17+
18+
static ConfigObject getGroup(String group) {
19+
ConfigObject groupConfig = Holders.getConfig()[BASE_CONFIG + ".groups." + group]
20+
21+
if (!groupConfig || groupConfig.isEmpty()) {
22+
throw new FileUploadException("No service found under [${BASE_CONFIG}.${group}]")
23+
}
24+
25+
return groupConfig
26+
}
27+
28+
static void verifyGroup(String group) {
29+
ConfigObject groupConfig = Holders.getConfig()[BASE_CONFIG + ".groups." + group]
30+
31+
if (!groupConfig || groupConfig.isEmpty()) {
32+
throw new FileUploadException("No service found under [${BASE_CONFIG}.${group}]")
33+
}
34+
35+
// TODO complete implementation
36+
}
37+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.wizpanda.file.api
2+
3+
import com.wizpanda.file.StoredFile
4+
import com.wizpanda.file.exception.FileUploadException
5+
import com.wizpanda.file.utils.FileUtils
6+
import org.springframework.web.multipart.MultipartFile
7+
8+
abstract class AbstractStorageApi implements StorageApi {
9+
10+
File rawFile
11+
StoredFile gormFile
12+
13+
StoredFile save(MultipartFile multipartFile) throws FileUploadException {
14+
File temporaryFile
15+
try {
16+
temporaryFile = FileUtils.saveTemporarily(multipartFile)
17+
} catch (IllegalStateException | IOException e) {
18+
throw new FileUploadException(e.message)
19+
}
20+
21+
return save(temporaryFile)
22+
}
23+
24+
StoredFile save(File rawFile) throws FileUploadException {
25+
this.rawFile = rawFile
26+
this.gormFile = new StoredFile()
27+
this.gormFile.originalName = rawFile.name
28+
this.gormFile.groupName = service.groupName
29+
this.gormFile.size = rawFile.size()
30+
31+
this.validateRawFile()
32+
this.saveNativeFile()
33+
34+
return this.saveGORMFile()
35+
}
36+
37+
@Override
38+
StoredFile saveGORMFile() throws FileUploadException {
39+
this.gormFile.save()
40+
41+
if (this.gormFile.hasErrors()) {
42+
println this.gormFile.errors
43+
// TODO Delete the native file and then throw the exception
44+
throw new FileUploadException()
45+
}
46+
47+
return this.gormFile
48+
}
49+
50+
@Override
51+
void delete(StoredFile file) {
52+
deleteNativeFile(file)
53+
}
54+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package com.wizpanda.file.api
2+
3+
import com.wizpanda.file.StoredFile
4+
import com.wizpanda.file.service.AmazonS3UploaderService
5+
import grails.util.Environment
6+
import grails.util.GrailsStringUtils
7+
import org.jclouds.ContextBuilder
8+
import org.jclouds.aws.s3.AWSS3Client
9+
import org.jclouds.blobstore.BlobStore
10+
import org.jclouds.blobstore.BlobStoreContext
11+
import org.jclouds.s3.domain.internal.MutableObjectMetadataImpl
12+
13+
import javax.activation.MimetypesFileTypeMap
14+
15+
abstract class AmazonS3Api extends AbstractStorageApi {
16+
17+
BlobStore blobStore
18+
AWSS3Client client
19+
BlobStoreContext context
20+
AmazonS3UploaderService service
21+
22+
void authenticate() {
23+
println service.accessKey
24+
println service.accessSecret
25+
context = ContextBuilder.newBuilder("aws-s3")
26+
.credentials(service.accessKey, service.accessSecret)
27+
.buildView(BlobStoreContext.class)
28+
println "Context created ${context.class}"
29+
30+
blobStore = context.getBlobStore()
31+
println "BlobStore ${blobStore.class}"
32+
33+
// Storing wrapped Api of S3Client with Apache JCloud
34+
client = context.unwrap().getApi()
35+
}
36+
37+
void close() {
38+
context.close()
39+
}
40+
41+
void setContentType(MutableObjectMetadataImpl mutableObjectMetadata, File file) {
42+
String contentType = new MimetypesFileTypeMap().getContentType(file.name)
43+
44+
if (contentType) {
45+
mutableObjectMetadata.getContentMetadata().setContentType(contentType)
46+
}
47+
}
48+
49+
@Override
50+
String getFileName(File file) {
51+
String name = UUID.randomUUID().toString()
52+
String originalFileName = file.name
53+
String extension = GrailsStringUtils.substringAfterLast(originalFileName, ".")
54+
55+
return name + "." + extension
56+
}
57+
58+
String getContainerName() {
59+
String name = service.container
60+
if (Environment.current != Environment.PRODUCTION) {
61+
name += "-" + Environment.current.name.toLowerCase()
62+
}
63+
64+
return name
65+
}
66+
67+
String getDirectory() {
68+
69+
}
70+
71+
@Override
72+
void validateRawFile() {
73+
74+
}
75+
76+
@Override
77+
void deleteNativeFile(StoredFile file) {
78+
// TODO implement me
79+
}
80+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.wizpanda.file.api
2+
3+
import com.wizpanda.file.StoredFile
4+
import com.wizpanda.file.service.AmazonS3UploaderService
5+
import org.jclouds.aws.s3.blobstore.options.AWSS3PutObjectOptions
6+
import org.jclouds.s3.domain.CannedAccessPolicy
7+
import org.jclouds.s3.domain.S3Object
8+
import org.jclouds.s3.domain.internal.MutableObjectMetadataImpl
9+
import org.jclouds.s3.domain.internal.S3ObjectImpl
10+
11+
class AmazonS3PermanentURLApi extends AmazonS3Api {
12+
13+
AmazonS3PermanentURLApi(AmazonS3UploaderService service) {
14+
this.service = service
15+
}
16+
17+
@Override
18+
void saveNativeFile() {
19+
this.authenticate()
20+
21+
AWSS3PutObjectOptions fileOptions = new AWSS3PutObjectOptions()
22+
fileOptions.withAcl(CannedAccessPolicy.PUBLIC_READ)
23+
24+
String fileName = getFileName(this.rawFile)
25+
String containerName = getContainerName()
26+
27+
MutableObjectMetadataImpl mutableObjectMetadata = new MutableObjectMetadataImpl()
28+
mutableObjectMetadata.setKey(fileName)
29+
setContentType(mutableObjectMetadata, this.rawFile)
30+
31+
S3Object s3Object = new S3ObjectImpl(mutableObjectMetadata)
32+
s3Object.setPayload(this.rawFile)
33+
34+
client.putObject(containerName, s3Object, fileOptions)
35+
36+
this.gormFile.name = fileName
37+
this.gormFile.url = client.getObject(containerName, fileName, null).metadata.uri
38+
39+
this.close()
40+
}
41+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.wizpanda.file.api
2+
3+
import com.wizpanda.file.StoredFile
4+
import com.wizpanda.file.exception.FileUploadException
5+
import com.wizpanda.file.service.UploaderService
6+
import org.springframework.web.multipart.MultipartFile
7+
8+
interface StorageApi {
9+
10+
UploaderService service
11+
12+
void saveNativeFile()
13+
14+
void validateRawFile()
15+
16+
void deleteNativeFile(StoredFile file)
17+
18+
String getFileName(File file)
19+
20+
StoredFile saveGORMFile() throws FileUploadException
21+
22+
/**
23+
* The entry point of the Storage API instance which will accept a raw file to be saved/uploaded via an API.
24+
* @param rawFile
25+
* @return Newly saved GORM StoredFile instance
26+
*/
27+
StoredFile save(File rawFile) throws FileUploadException
28+
29+
/**
30+
* The entry point of the Storage API instance which will accept an uploaded multipart file to be saved/uploaded via
31+
* an API.
32+
* @param multipartFile
33+
* @return Newly saved GORM StoredFile instance
34+
*/
35+
StoredFile save(MultipartFile multipartFile) throws FileUploadException
36+
37+
void delete(StoredFile file)
38+
}

0 commit comments

Comments
 (0)