Skip to content

Commit cad20e9

Browse files
authored
Merge pull request #391 from awsdocs/update-s3-java-example
Update s3 java example
2 parents 4c88540 + 716117f commit cad20e9

File tree

7 files changed

+159
-104
lines changed

7 files changed

+159
-104
lines changed

sample-apps/s3-java/README.md

+12-3
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/build.gradle

+8-7
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,19 @@ repositories {
77
}
88

99
dependencies {
10-
implementation platform('com.amazonaws:aws-xray-recorder-sdk-bom:2.4.0')
10+
implementation platform('software.amazon.awssdk:bom:2.15.0')
11+
implementation platform('com.amazonaws:aws-xray-recorder-sdk-bom:2.11.0')
12+
implementation 'software.amazon.awssdk:s3'
1113
implementation 'com.amazonaws:aws-lambda-java-core:1.2.1'
12-
implementation 'com.amazonaws:aws-lambda-java-events:2.2.9'
13-
implementation 'com.amazonaws:aws-java-sdk-s3:1.11.578'
14+
implementation 'com.amazonaws:aws-lambda-java-events:3.11.0'
15+
implementation 'org.apache.logging.log4j:log4j-api:[2.17.1,)'
16+
implementation 'org.apache.logging.log4j:log4j-core:[2.17.1,)'
17+
implementation 'org.apache.logging.log4j:log4j-slf4j18-impl:[2.17.1,)'
18+
runtimeOnly 'com.amazonaws:aws-lambda-java-log4j2:1.5.1'
1419
implementation 'com.amazonaws:aws-xray-recorder-sdk-core'
1520
implementation 'com.amazonaws:aws-xray-recorder-sdk-aws-sdk'
1621
implementation 'com.amazonaws:aws-xray-recorder-sdk-aws-sdk-instrumentor'
1722
implementation 'com.google.code.gson:gson:2.8.6'
18-
implementation 'org.apache.logging.log4j:log4j-api:[2.17.1,)'
19-
implementation 'org.apache.logging.log4j:log4j-core:[2.17.1,)'
20-
implementation 'org.apache.logging.log4j:log4j-slf4j18-impl:[2.17.1,)'
21-
runtimeOnly 'com.amazonaws:aws-lambda-java-log4j2:1.5.0'
2223
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0'
2324
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.6.0'
2425
}

sample-apps/s3-java/pom.xml

+32-21
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,45 @@
1111
<maven.compiler.source>1.8</maven.compiler.source>
1212
<maven.compiler.target>1.8</maven.compiler.target>
1313
</properties>
14+
15+
<dependencyManagement>
16+
<dependencies>
17+
<dependency>
18+
<groupId>software.amazon.awssdk</groupId>
19+
<artifactId>bom</artifactId>
20+
<version>2.16.1</version>
21+
<type>pom</type>
22+
<scope>import</scope>
23+
</dependency>
24+
<dependency>
25+
<groupId>com.amazonaws</groupId>
26+
<artifactId>aws-xray-recorder-sdk-bom</artifactId>
27+
<version>2.11.0</version>
28+
<type>pom</type>
29+
<scope>import</scope>
30+
</dependency>
31+
</dependencies>
32+
</dependencyManagement>
33+
1434
<dependencies>
35+
<dependency>
36+
<groupId>software.amazon.awssdk</groupId>
37+
<artifactId>s3</artifactId>
38+
</dependency>
1539
<dependency>
1640
<groupId>com.amazonaws</groupId>
1741
<artifactId>aws-lambda-java-core</artifactId>
18-
<version>1.2.0</version>
42+
<version>1.2.1</version>
1943
</dependency>
2044
<dependency>
2145
<groupId>com.amazonaws</groupId>
2246
<artifactId>aws-lambda-java-events</artifactId>
23-
<version>2.2.7</version>
47+
<version>3.11.0</version>
2448
</dependency>
2549
<dependency>
2650
<groupId>com.amazonaws</groupId>
2751
<artifactId>aws-lambda-java-log4j2</artifactId>
28-
<version>1.5.0</version>
29-
</dependency>
30-
<dependency>
31-
<groupId>com.google.code.gson</groupId>
32-
<artifactId>gson</artifactId>
33-
<version>2.8.6</version>
52+
<version>1.5.1</version>
3453
</dependency>
3554
<dependency>
3655
<groupId>org.apache.logging.log4j</groupId>
@@ -47,30 +66,22 @@
4766
<artifactId>log4j-slf4j18-impl</artifactId>
4867
<version>[2.17.1,)</version>
4968
</dependency>
50-
<dependency>
51-
<groupId>com.amazonaws</groupId>
52-
<artifactId>aws-java-sdk-s3</artifactId>
53-
<version>1.11.578</version>
54-
</dependency>
5569
<dependency>
5670
<groupId>com.amazonaws</groupId>
5771
<artifactId>aws-xray-recorder-sdk-core</artifactId>
58-
<version>2.4.0</version>
59-
</dependency>
60-
<dependency>
61-
<groupId>com.amazonaws</groupId>
62-
<artifactId>aws-xray-recorder-sdk-aws-sdk-core</artifactId>
63-
<version>2.4.0</version>
6472
</dependency>
6573
<dependency>
6674
<groupId>com.amazonaws</groupId>
6775
<artifactId>aws-xray-recorder-sdk-aws-sdk</artifactId>
68-
<version>2.4.0</version>
6976
</dependency>
7077
<dependency>
7178
<groupId>com.amazonaws</groupId>
7279
<artifactId>aws-xray-recorder-sdk-aws-sdk-instrumentor</artifactId>
73-
<version>2.4.0</version>
80+
</dependency>
81+
<dependency>
82+
<groupId>com.google.code.gson</groupId>
83+
<artifactId>gson</artifactId>
84+
<version>2.8.6</version>
7485
</dependency>
7586
<dependency>
7687
<groupId>org.junit.jupiter</groupId>

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

+97-61
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,24 @@
88
import java.io.ByteArrayOutputStream;
99
import java.io.IOException;
1010
import java.io.InputStream;
11+
import java.util.HashMap;
12+
import java.util.Map;
1113
import java.util.regex.Matcher;
1214
import java.util.regex.Pattern;
1315

1416
import javax.imageio.ImageIO;
1517

16-
import com.amazonaws.AmazonServiceException;
18+
import software.amazon.awssdk.awscore.exception.AwsServiceException;
19+
import software.amazon.awssdk.core.sync.RequestBody;
20+
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
21+
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
22+
import software.amazon.awssdk.services.s3.model.S3Object;
23+
import software.amazon.awssdk.services.s3.S3Client;
24+
1725
import com.amazonaws.services.lambda.runtime.Context;
1826
import com.amazonaws.services.lambda.runtime.RequestHandler;
1927
import com.amazonaws.services.lambda.runtime.events.S3Event;
20-
import com.amazonaws.services.s3.AmazonS3;
21-
import com.amazonaws.services.s3.event.S3EventNotification.S3EventNotificationRecord;
22-
import com.amazonaws.services.s3.model.GetObjectRequest;
23-
import com.amazonaws.services.s3.model.ObjectMetadata;
24-
import com.amazonaws.services.s3.model.S3Object;
25-
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
28+
import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification.S3EventNotificationRecord;
2629

2730
import com.google.gson.Gson;
2831
import com.google.gson.GsonBuilder;
@@ -34,12 +37,12 @@
3437
public class Handler implements RequestHandler<S3Event, String> {
3538
Gson gson = new GsonBuilder().setPrettyPrinting().create();
3639
private static final Logger logger = LoggerFactory.getLogger(Handler.class);
37-
private static final float MAX_WIDTH = 100;
38-
private static final float MAX_HEIGHT = 100;
39-
private final String JPG_TYPE = (String) "jpg";
40-
private final String JPG_MIME = (String) "image/jpeg";
41-
private final String PNG_TYPE = (String) "png";
42-
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";
4346
@Override
4447
public String handleRequest(S3Event s3event, Context context) {
4548
try {
@@ -55,7 +58,7 @@ public String handleRequest(S3Event s3event, Context context) {
5558
String dstKey = "resized-" + srcKey;
5659

5760
// Infer the image type.
58-
Matcher matcher = Pattern.compile(".*\\.([^\\.]*)").matcher(srcKey);
61+
Matcher matcher = Pattern.compile(REGEX).matcher(srcKey);
5962
if (!matcher.matches()) {
6063
logger.info("Unable to infer image type for key " + srcKey);
6164
return "";
@@ -67,63 +70,96 @@ public String handleRequest(S3Event s3event, Context context) {
6770
}
6871

6972
// Download the image from S3 into a stream
70-
AmazonS3 s3Client = AmazonS3ClientBuilder.defaultClient();
71-
S3Object s3Object = s3Client.getObject(new GetObjectRequest(
72-
srcBucket, srcKey));
73-
InputStream objectData = s3Object.getObjectContent();
74-
75-
// Read the source image
76-
BufferedImage srcImage = ImageIO.read(objectData);
77-
int srcHeight = srcImage.getHeight();
78-
int srcWidth = srcImage.getWidth();
79-
// Infer the scaling factor to avoid stretching the image
80-
// unnaturally
81-
float scalingFactor = Math.min(MAX_WIDTH / srcWidth, MAX_HEIGHT
82-
/ srcHeight);
83-
int width = (int) (scalingFactor * srcWidth);
84-
int height = (int) (scalingFactor * srcHeight);
85-
86-
BufferedImage resizedImage = new BufferedImage(width, height,
87-
BufferedImage.TYPE_INT_RGB);
88-
Graphics2D g = resizedImage.createGraphics();
89-
// Fill with white before applying semi-transparent (alpha) images
90-
g.setPaint(Color.white);
91-
g.fillRect(0, 0, width, height);
92-
// Simple bilinear resize
93-
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
94-
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
95-
g.drawImage(srcImage, 0, 0, width, height, null);
96-
g.dispose();
73+
S3Client s3Client = S3Client.builder().build();
74+
InputStream s3Object = getObject(s3Client, srcBucket, srcKey);
75+
76+
// Read the source image and resize it
77+
BufferedImage srcImage = ImageIO.read(s3Object);
78+
BufferedImage newImage = resizeImage(srcImage);
9779

9880
// Re-encode image to target format
99-
ByteArrayOutputStream os = new ByteArrayOutputStream();
100-
ImageIO.write(resizedImage, imageType, os);
101-
InputStream is = new ByteArrayInputStream(os.toByteArray());
102-
// Set Content-Length and Content-Type
103-
ObjectMetadata meta = new ObjectMetadata();
104-
meta.setContentLength(os.size());
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);
86+
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) {
105+
Map<String, String> metadata = new HashMap<>();
106+
metadata.put("Content-Length", Integer.toString(outputStream.size()));
105107
if (JPG_TYPE.equals(imageType)) {
106-
meta.setContentType(JPG_MIME);
107-
}
108-
if (PNG_TYPE.equals(imageType)) {
109-
meta.setContentType(PNG_MIME);
108+
metadata.put("Content-Type", JPG_MIME);
109+
} else if (PNG_TYPE.equals(imageType)) {
110+
metadata.put("Content-Type", PNG_MIME);
110111
}
111112

113+
PutObjectRequest putObjectRequest = PutObjectRequest.builder()
114+
.bucket(bucket)
115+
.key(key)
116+
.metadata(metadata)
117+
.build();
118+
112119
// Uploading to S3 destination bucket
113-
logger.info("Writing to: " + dstBucket + "/" + dstKey);
120+
logger.info("Writing to: " + bucket + "/" + key);
114121
try {
115-
s3Client.putObject(dstBucket, dstKey, is, meta);
122+
s3Client.putObject(putObjectRequest,
123+
RequestBody.fromBytes(outputStream.toByteArray()));
116124
}
117-
catch(AmazonServiceException e)
125+
catch(AwsServiceException e)
118126
{
119-
logger.error(e.getErrorMessage());
127+
logger.error(e.awsErrorDetails().errorMessage());
120128
System.exit(1);
121129
}
122-
logger.info("Successfully resized " + srcBucket + "/"
123-
+ srcKey + " and uploaded to " + dstBucket + "/" + dstKey);
124-
return "Ok";
125-
} catch (IOException e) {
126-
throw new RuntimeException(e);
127-
}
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;
128164
}
129165
}

sample-apps/s3-java/src/test/java/example/InvokeTest.java

+8-10
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,14 @@
88
import com.amazonaws.services.lambda.runtime.LambdaLogger;
99
import com.amazonaws.services.lambda.runtime.RequestHandler;
1010
import com.amazonaws.services.lambda.runtime.events.S3Event;
11-
import com.amazonaws.services.s3.event.S3EventNotification;
12-
import com.amazonaws.services.s3.event.S3EventNotification.S3EventNotificationRecord;
13-
import com.amazonaws.services.s3.event.S3EventNotification.RequestParametersEntity;
14-
import com.amazonaws.services.s3.event.S3EventNotification.ResponseElementsEntity;
15-
import com.amazonaws.services.s3.event.S3EventNotification.S3Entity;
16-
import com.amazonaws.services.s3.event.S3EventNotification.UserIdentityEntity;
17-
import com.amazonaws.services.s3.event.S3EventNotification.GlacierEventDataEntity;
18-
import com.amazonaws.services.s3.event.S3EventNotification.S3BucketEntity;
19-
import com.amazonaws.services.s3.event.S3EventNotification.S3ObjectEntity;
20-
import com.amazonaws.services.s3.event.S3EventNotification.UserIdentityEntity;
11+
import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification;
12+
import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification.RequestParametersEntity;
13+
import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification.ResponseElementsEntity;
14+
import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification.S3BucketEntity;
15+
import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification.S3Entity;
16+
import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification.S3EventNotificationRecord;
17+
import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification.S3ObjectEntity;
18+
import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification.UserIdentityEntity;
2119

2220
import org.slf4j.Logger;
2321
import org.slf4j.LoggerFactory;

sample-apps/s3-java/template-mvn.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Resources:
1313
Runtime: java8
1414
Description: Java function
1515
MemorySize: 512
16-
Timeout: 10
16+
Timeout: 30
1717
# Function's execution role
1818
Policies:
1919
- AWSLambdaBasicExecutionRole

0 commit comments

Comments
 (0)