Skip to content
This repository was archived by the owner on Nov 25, 2017. It is now read-only.

Commit a941198

Browse files
author
Mattias Levin
committed
Add S3 Image store implementation
1 parent 17e82af commit a941198

File tree

7 files changed

+224
-76
lines changed

7 files changed

+224
-76
lines changed

app/Module.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import com.google.inject.AbstractModule;
22
import java.time.Clock;
33

4-
import services.ApplicationTimer;
5-
import services.AtomicCounter;
6-
import services.Counter;
4+
import org.springframework.cglib.core.Local;
5+
import services.*;
76

87
/**
98
* This class is a Guice module that tells Guice how to bind several
@@ -19,6 +18,9 @@ public class Module extends AbstractModule {
1918

2019
@Override
2120
public void configure() {
21+
22+
bind(ImageStore.class).to(S3ImageStore.class);
23+
2224
// Use the system clock as the default implementation of Clock
2325
bind(Clock.class).toInstance(Clock.systemDefaultZone());
2426
// Ask Guice to create an instance of ApplicationTimer when the

app/controllers/ImageController.java

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,13 @@
77
import play.mvc.Http;
88
import play.mvc.Result;
99
import services.ImageStore;
10+
import services.LocalFileImageStore;
1011

12+
import java.awt.*;
1113
import java.io.File;
1214
import java.io.IOException;
15+
import java.io.InputStream;
16+
import java.net.URL;
1317
import java.nio.file.Path;
1418

1519
public class ImageController extends Controller {
@@ -38,38 +42,32 @@ public Result uploadImage() {
3842
return badRequest("Only png images are supported");
3943
}
4044

41-
final Path source = image.getFile().toPath();
4245
try {
43-
final String imageId = imageStore.storeImage(source);
44-
final String downloadUrl = routes.ImageController.downloadImage(imageId).absoluteURL(request());
46+
final String imageId = imageStore.save(image.getFile());
47+
final URL downloadUrl = imageStore.downloadUrl(imageId, request());
4548
Logger.debug("Download url: {}", downloadUrl);
4649

4750
return ok(Json.toJson(downloadUrl));
48-
} catch (IOException e) {
51+
} catch (Exception e) {
4952
e.printStackTrace();
5053
return internalServerError("Failed to store uploaded file");
5154
}
5255

5356
}
5457

5558
public Result downloadImage(String id) {
56-
final File file = imageStore.getImage(id);
57-
if (null == file) {
59+
final InputStream stream = imageStore.get(id);
60+
if (null == stream) {
5861
return notFound("Image not found");
5962
}
60-
return ok(file);
63+
return ok(stream);
6164
}
6265

6366
public Result deleteImage(String id) {
64-
try {
65-
if (!imageStore.deleteImage(id)) {
66-
notFound("Image not found");
67-
}
68-
return ok();
69-
} catch (IOException e) {
70-
e.printStackTrace();
71-
return internalServerError("Failed to delete image file");
67+
if (!imageStore.delete(id)) {
68+
notFound("Image not found");
7269
}
70+
return ok();
7371
}
7472

7573
}

app/services/ImageStore.java

Lines changed: 9 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,20 @@
11
package services;
22

3-
import play.Logger;
43

5-
import java.io.File;
6-
import java.io.IOException;
7-
import java.nio.file.Files;
8-
import java.nio.file.Path;
9-
import java.nio.file.Paths;
10-
import java.util.UUID;
11-
12-
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
13-
14-
public class ImageStore {
15-
16-
private static final Path IMAGES_ROOT = Paths.get("/tmp/play/images");
17-
18-
public ImageStore() {
19-
File rootDir = IMAGES_ROOT.toFile();
20-
if (!rootDir.exists() && rootDir.mkdirs()) {
21-
Logger.error("Failed to create image upload directory");
22-
}
23-
}
24-
25-
public String storeImage(Path source) throws IOException {
26-
27-
final String imageId = createImageId();
28-
final Path target = createImagePath(imageId + ".png");
4+
import play.mvc.Http;
295

30-
Logger.debug("source: {} target: {}", source, target);
31-
32-
Files.move(source, target, REPLACE_EXISTING);
33-
Logger.debug("Upload file: {}, to path: {}", source, target);
34-
35-
return imageId;
36-
}
37-
38-
public File getImage(String id) {
39-
40-
final File file = createImagePath(id + ".png").toFile();
41-
if (!file.isFile()) {
42-
return null;
43-
}
44-
45-
return file;
46-
}
6+
import java.io.File;
7+
import java.io.InputStream;
8+
import java.net.URL;
479

48-
public boolean deleteImage(String id) throws IOException {
10+
public interface ImageStore {
4911

50-
final Path path = createImagePath(id + ".png");
51-
if (!path.toFile().isFile()) {
52-
return false;
53-
}
12+
String save(File file);
5413

55-
Files.deleteIfExists(path);
56-
return true;
57-
}
14+
InputStream get(String id);
5815

59-
private static Path createImagePath(String imageId) {
60-
return IMAGES_ROOT.resolve(imageId);
61-
}
16+
boolean delete(String id);
6217

63-
private static String createImageId() {
64-
final UUID uuid = UUID.randomUUID();
65-
return uuid.toString();
66-
}
18+
URL downloadUrl(String id, Http.Request request);
6719

6820
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package services;
2+
3+
import controllers.routes;
4+
import play.Logger;
5+
import play.mvc.Http;
6+
7+
import java.io.*;
8+
import java.net.MalformedURLException;
9+
import java.net.URL;
10+
import java.nio.file.Files;
11+
import java.nio.file.Path;
12+
import java.nio.file.Paths;
13+
import java.util.UUID;
14+
15+
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
16+
17+
public class LocalFileImageStore implements ImageStore {
18+
19+
private static final Path IMAGES_ROOT = Paths.get("/tmp/play/images");
20+
21+
public LocalFileImageStore() {
22+
23+
File rootDir = IMAGES_ROOT.toFile();
24+
if (!rootDir.exists() && rootDir.mkdirs()) {
25+
Logger.error("Failed to create image upload directory");
26+
}
27+
}
28+
29+
public String save(File file) {
30+
31+
final Path source = file.toPath();
32+
final String imageId = createImageId();
33+
final Path target = createImagePath(imageId + ".png");
34+
35+
Logger.debug("source: {} target: {}", source, target);
36+
37+
try {
38+
Files.move(source, target, REPLACE_EXISTING);
39+
Logger.debug("Upload file: {}, to path: {}", source, target);
40+
return imageId;
41+
} catch (IOException e) {
42+
e.printStackTrace();
43+
return null;
44+
}
45+
46+
}
47+
48+
public InputStream get(String id) {
49+
50+
final File file = createImagePath(id + ".png").toFile();
51+
try {
52+
return new FileInputStream(file);
53+
} catch (FileNotFoundException e) {
54+
e.printStackTrace();
55+
return null;
56+
}
57+
}
58+
59+
public boolean delete(String id) {
60+
61+
final Path path = createImagePath(id + ".png");
62+
if (!path.toFile().isFile()) {
63+
return false;
64+
}
65+
66+
try {
67+
Files.deleteIfExists(path);
68+
return true;
69+
} catch (IOException e) {
70+
e.printStackTrace();
71+
return false;
72+
}
73+
}
74+
75+
@Override
76+
public URL downloadUrl(String id, Http.Request request) {
77+
try {
78+
final String downloadUrl = routes.ImageController.downloadImage(id).absoluteURL(request);
79+
return new URL(downloadUrl);
80+
} catch (MalformedURLException e) {
81+
e.printStackTrace();
82+
return null;
83+
}
84+
}
85+
86+
private static Path createImagePath(String imageId) {
87+
return IMAGES_ROOT.resolve(imageId);
88+
}
89+
90+
private static String createImageId() {
91+
final UUID uuid = UUID.randomUUID();
92+
return uuid.toString();
93+
}
94+
95+
}

app/services/S3ImageStore.java

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package services;
2+
3+
import com.amazonaws.auth.AWSStaticCredentialsProvider;
4+
import com.amazonaws.auth.BasicAWSCredentials;
5+
import com.amazonaws.regions.Regions;
6+
import com.amazonaws.services.s3.AmazonS3;
7+
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
8+
import com.amazonaws.services.s3.model.*;
9+
import play.Application;
10+
import play.Configuration;
11+
import play.mvc.Http;
12+
13+
import javax.inject.Inject;
14+
import java.io.File;
15+
import java.io.InputStream;
16+
import java.net.MalformedURLException;
17+
import java.net.URL;
18+
import java.util.UUID;
19+
20+
public class S3ImageStore implements ImageStore {
21+
22+
private static final String AWS_S3_BUCKET = "aws.s3.bucket";
23+
private static final String AWS_ACCESS_KEY = "aws.access.key";
24+
private static final String AWS_SECRET_KEY = "aws.secret.key";
25+
26+
private final String bucket;
27+
28+
private final AmazonS3 s3Client;
29+
30+
@Inject
31+
public S3ImageStore(Configuration config) {
32+
33+
// You can create your own Amazon S3 bucket
34+
// There is a free tier up to a limit (https://aws.amazon.com/s3/pricing/)
35+
// Key and secret should never be stored in the source code (some one can get it from Github)
36+
// Instead export them as environment variable in your Terminal
37+
// $ export AWS_ACCESS_KEY=<Your AWS Access Key>
38+
// $ export AWS_SECRET_KEY=<Your AWS Secret Key>
39+
// When you compile your code the application.config will be updated with these values
40+
String accessKey = config.getString(AWS_ACCESS_KEY);
41+
String secretKey = config.getString(AWS_SECRET_KEY);
42+
BasicAWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
43+
44+
s3Client = AmazonS3ClientBuilder.standard()
45+
.withRegion(Regions.AP_SOUTH_1)
46+
.withCredentials(new AWSStaticCredentialsProvider(credentials))
47+
.build();
48+
49+
bucket = config.getString(AWS_S3_BUCKET);
50+
}
51+
52+
@Override
53+
public String save(File file) {
54+
55+
final String key = getKey();
56+
57+
PutObjectRequest putObjectRequest = new PutObjectRequest(bucket, key, file);
58+
putObjectRequest.withCannedAcl(CannedAccessControlList.PublicRead);
59+
s3Client.putObject(putObjectRequest);
60+
61+
return key;
62+
}
63+
64+
@Override
65+
public InputStream get(String id) {
66+
S3Object object = s3Client.getObject(bucket, id);
67+
S3ObjectInputStream objectContent = object.getObjectContent();
68+
return objectContent;
69+
}
70+
71+
@Override
72+
public boolean delete(String id) {
73+
DeleteObjectsRequest dor = new DeleteObjectsRequest(bucket)
74+
.withKeys(id);
75+
s3Client.deleteObjects(dor);
76+
return true;
77+
}
78+
79+
@Override
80+
public URL downloadUrl(String id, Http.Request request) {
81+
82+
try {
83+
return new URL("https://" + bucket + ".s3.amazonaws.com/" + id);
84+
} catch (MalformedURLException e) {
85+
e.printStackTrace();
86+
return null;
87+
}
88+
}
89+
90+
private static String getKey() {
91+
return UUID.randomUUID().toString() + ".png";
92+
}
93+
94+
}

build.sbt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ libraryDependencies ++= Seq(
1212
javaWs,
1313
javaJpa,
1414
"org.hibernate" % "hibernate-entitymanager" % "5.1.0.Final",
15-
"mysql" % "mysql-connector-java" % "5.1.36"
15+
"mysql" % "mysql-connector-java" % "5.1.36",
16+
"com.amazonaws" % "aws-java-sdk" % "1.11.109"
1617
)

conf/application.conf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,3 +355,9 @@ db {
355355
}
356356

357357
jpa.default=defaultPersistenceUnit
358+
359+
aws {
360+
access.key=${?AWS_ACCESS_KEY}
361+
secret.key=${?AWS_SECRET_KEY}
362+
s3.bucket=com.mattias.levin.gnits
363+
}

0 commit comments

Comments
 (0)