Skip to content

Commit d00fb82

Browse files
committed
Refactor the code into logical function blocks for increased readability and abstraction.
- Pulled out code from main handler into separate private functions. - Tested the scripts E2E to make sure they're still working. - Added additional comments in function code to explain steps. - Updated README with more explanations on what the function is doing, instead of just listing steps.
1 parent ea8d8a2 commit d00fb82

File tree

2 files changed

+86
-49
lines changed

2 files changed

+86
-49
lines changed

sample-apps/s3-java/README.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
The project source includes function code and supporting resources:
66

7-
- `src/main` - A Java function.
7+
- `src/main` - A Java Lambda function that scales down an image stored in S3.
88
- `src/test` - A unit test and helper classes.
99
- `template.yml` - An AWS CloudFormation template that creates an application.
1010
- `build.gradle` - A Gradle build file.
@@ -63,10 +63,14 @@ You can also build the application with Maven. To use maven, add `mvn` to the co
6363
...
6464

6565
# Test
66-
To upload an image file to the application bucket and trigger the function, run `4-upload.sh`.
66+
This Lambda function takes an image that's currently stored in S3, and scales it down into
67+
a thumbnail-sized image. To upload an image file to the application bucket, run `4-upload.sh`.
6768

6869
s3-java$ ./4-upload.sh
6970

71+
In your `s3-java-bucket-<random_uuid>` bucket that was created in step 3, you should now see a
72+
key `inbound/sample-s3-java.png` file, which represents the original image.
73+
7074
To invoke the function directly, run `5-invoke.sh`.
7175

7276
s3-java$ ./5-invoke.sh
@@ -75,7 +79,12 @@ To invoke the function directly, run `5-invoke.sh`.
7579
"ExecutedVersion": "$LATEST"
7680
}
7781

78-
Let the script invoke the function a few times and then press `CRTL+C` to exit.
82+
Let the script invoke the function a few times and then press `CRTL+C` to exit. Note that you
83+
may see function timeouts in the first few iterations due to cold starts; after a while, they
84+
should begin to succeed.
85+
86+
If you look at the `s3-java-bucket-<random_uuid>` bucket in your account, you should now see a
87+
key `resized-inbound/sample-s3-java.png` file, which represents the new, shrunken image.
7988

8089
The application uses AWS X-Ray to trace requests. Open the [X-Ray console](https://console.aws.amazon.com/xray/home#/service-map) to view the service map.
8190

sample-apps/s3-java/src/main/java/example/Handler.java

Lines changed: 74 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,12 @@
3737
public class Handler implements RequestHandler<S3Event, String> {
3838
Gson gson = new GsonBuilder().setPrettyPrinting().create();
3939
private static final Logger logger = LoggerFactory.getLogger(Handler.class);
40-
private static final float MAX_WIDTH = 100;
41-
private static final float MAX_HEIGHT = 100;
42-
private final String JPG_TYPE = (String) "jpg";
43-
private final String JPG_MIME = (String) "image/jpeg";
44-
private final String PNG_TYPE = (String) "png";
45-
private final String PNG_MIME = (String) "image/png";
40+
private static final float MAX_DIMENSION = 100;
41+
private final String REGEX = ".*\\.([^\\.]*)";
42+
private final String JPG_TYPE = "jpg";
43+
private final String JPG_MIME = "image/jpeg";
44+
private final String PNG_TYPE = "png";
45+
private final String PNG_MIME = "image/png";
4646
@Override
4747
public String handleRequest(S3Event s3event, Context context) {
4848
try {
@@ -58,7 +58,7 @@ public String handleRequest(S3Event s3event, Context context) {
5858
String dstKey = "resized-" + srcKey;
5959

6060
// Infer the image type.
61-
Matcher matcher = Pattern.compile(".*\\.([^\\.]*)").matcher(srcKey);
61+
Matcher matcher = Pattern.compile(REGEX).matcher(srcKey);
6262
if (!matcher.matches()) {
6363
logger.info("Unable to infer image type for key " + srcKey);
6464
return "";
@@ -71,67 +71,95 @@ public String handleRequest(S3Event s3event, Context context) {
7171

7272
// Download the image from S3 into a stream
7373
S3Client s3Client = S3Client.builder().build();
74-
GetObjectRequest getObjectRequest = GetObjectRequest.builder()
75-
.bucket(srcBucket)
76-
.key(srcKey)
77-
.build();
78-
InputStream s3Object = s3Client.getObject(getObjectRequest);
74+
InputStream s3Object = getObject(s3Client, srcBucket, srcKey);
7975

80-
// Read the source image
76+
// Read the source image and resize it
8177
BufferedImage srcImage = ImageIO.read(s3Object);
82-
int srcHeight = srcImage.getHeight();
83-
int srcWidth = srcImage.getWidth();
84-
// Infer scaling factor to avoid stretching image unnaturally
85-
float scalingFactor = Math.min(
86-
MAX_WIDTH / srcWidth, MAX_HEIGHT / srcHeight);
87-
int width = (int) (scalingFactor * srcWidth);
88-
int height = (int) (scalingFactor * srcHeight);
89-
90-
BufferedImage resizedImage = new BufferedImage(width, height,
91-
BufferedImage.TYPE_INT_RGB);
92-
Graphics2D g = resizedImage.createGraphics();
93-
// Fill with white before applying semi-transparent (alpha) images
94-
g.setPaint(Color.white);
95-
g.fillRect(0, 0, width, height);
96-
// Simple bilinear resize
97-
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
98-
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
99-
g.drawImage(srcImage, 0, 0, width, height, null);
100-
g.dispose();
78+
BufferedImage newImage = resizeImage(srcImage);
10179

10280
// Re-encode image to target format
103-
ByteArrayOutputStream os = new ByteArrayOutputStream();
104-
ImageIO.write(resizedImage, imageType, os);
105-
// InputStream is = new ByteArrayInputStream(os.toByteArray());
81+
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
82+
ImageIO.write(newImage, imageType, outputStream);
83+
84+
// Upload new image to S3
85+
putObject(s3Client, outputStream, dstBucket, dstKey, imageType);
10686

87+
logger.info("Successfully resized " + srcBucket + "/"
88+
+ srcKey + " and uploaded to " + dstBucket + "/" + dstKey);
89+
return "Ok";
90+
} catch (IOException e) {
91+
throw new RuntimeException(e);
92+
}
93+
}
94+
95+
private InputStream getObject(S3Client s3Client, String bucket, String key) {
96+
GetObjectRequest getObjectRequest = GetObjectRequest.builder()
97+
.bucket(bucket)
98+
.key(key)
99+
.build();
100+
return s3Client.getObject(getObjectRequest);
101+
}
102+
103+
private void putObject(S3Client s3Client, ByteArrayOutputStream outputStream,
104+
String bucket, String key, String imageType) {
107105
Map<String, String> metadata = new HashMap<>();
108-
metadata.put("Content-Length", Integer.toString(os.size()));
106+
metadata.put("Content-Length", Integer.toString(outputStream.size()));
109107
if (JPG_TYPE.equals(imageType)) {
110108
metadata.put("Content-Type", JPG_MIME);
111109
} else if (PNG_TYPE.equals(imageType)) {
112110
metadata.put("Content-Type", PNG_MIME);
113111
}
112+
114113
PutObjectRequest putObjectRequest = PutObjectRequest.builder()
115-
.bucket(dstBucket)
116-
.key(dstKey)
114+
.bucket(bucket)
115+
.key(key)
117116
.metadata(metadata)
118117
.build();
119118

120119
// Uploading to S3 destination bucket
121-
logger.info("Writing to: " + dstBucket + "/" + dstKey);
120+
logger.info("Writing to: " + bucket + "/" + key);
122121
try {
123-
s3Client.putObject(putObjectRequest, RequestBody.fromBytes(os.toByteArray()));
122+
s3Client.putObject(putObjectRequest,
123+
RequestBody.fromBytes(outputStream.toByteArray()));
124124
}
125125
catch(AwsServiceException e)
126126
{
127127
logger.error(e.awsErrorDetails().errorMessage());
128128
System.exit(1);
129129
}
130-
logger.info("Successfully resized " + srcBucket + "/"
131-
+ srcKey + " and uploaded to " + dstBucket + "/" + dstKey);
132-
return "Ok";
133-
} catch (IOException e) {
134-
throw new RuntimeException(e);
135-
}
130+
}
131+
132+
/**
133+
* Resizes (shrinks) an image into a small, thumbnail-sized image.
134+
*
135+
* The new image is scaled down proportionally based on the source
136+
* image. The scaling factor is determined based on the value of
137+
* MAX_DIMENSION. The resulting new image has max(height, width)
138+
* = MAX_DIMENSION.
139+
*
140+
* @param srcImage BufferedImage to resize.
141+
* @return New BufferedImage that is scaled down to thumbnail size.
142+
*/
143+
private BufferedImage resizeImage(BufferedImage srcImage) {
144+
int srcHeight = srcImage.getHeight();
145+
int srcWidth = srcImage.getWidth();
146+
// Infer scaling factor to avoid stretching image unnaturally
147+
float scalingFactor = Math.min(
148+
MAX_DIMENSION / srcWidth, MAX_DIMENSION / srcHeight);
149+
int width = (int) (scalingFactor * srcWidth);
150+
int height = (int) (scalingFactor * srcHeight);
151+
152+
BufferedImage resizedImage = new BufferedImage(width, height,
153+
BufferedImage.TYPE_INT_RGB);
154+
Graphics2D graphics = resizedImage.createGraphics();
155+
// Fill with white before applying semi-transparent (alpha) images
156+
graphics.setPaint(Color.white);
157+
graphics.fillRect(0, 0, width, height);
158+
// Simple bilinear resize
159+
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
160+
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
161+
graphics.drawImage(srcImage, 0, 0, width, height, null);
162+
graphics.dispose();
163+
return resizedImage;
136164
}
137165
}

0 commit comments

Comments
 (0)