Skip to content

Commit c08a29c

Browse files
author
Michael Kaiser
committed
Add comments explaining the code
1 parent 3cb2f15 commit c08a29c

File tree

4 files changed

+126
-21
lines changed

4 files changed

+126
-21
lines changed

java/s3-object-lambda/infra/src/main/java/com/myorg/S3ObjectLambdaApp.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
import software.amazon.awscdk.App;
44
import software.amazon.awscdk.StackProps;
55

6+
/**
7+
* Main CDK application class that serves as the entry point for deploying the S3 Object Lambda infrastructure.
8+
*/
69
public class S3ObjectLambdaApp extends App {
710

811
public static void main(final String... args) {
@@ -12,6 +15,9 @@ public static void main(final String... args) {
1215
app.synth();
1316
}
1417

18+
/**
19+
* Creates a new instance of the S3ObjectLambdaStack with the specified properties.
20+
*/
1521
public S3ObjectLambdaStack createStack(StackProps stackProps) {
1622
return new S3ObjectLambdaStack(this, "S3ObjectLambdaStack", stackProps);
1723
}

java/s3-object-lambda/infra/src/main/java/com/myorg/S3ObjectLambdaStack.java

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,28 @@
2323
import static software.amazon.awscdk.services.s3objectlambda.CfnAccessPoint.*;
2424

2525
public class S3ObjectLambdaStack extends Stack {
26-
2726
private static final String S3_ACCESS_POINT_NAME = "s3-access-point";
2827
private static final String OBJECT_LAMBDA_ACCESS_POINT_NAME = "object-lambda-access-point";
2928

29+
/**
30+
* Constructs a new S3ObjectLambdaStack.
31+
*/
3032
public S3ObjectLambdaStack(final Construct scope, final String id, final StackProps props) {
31-
super(scope, id, props);
33+
super(scope, id, props);
34+
35+
// Construct the access point ARN using the region, account ID and access point name
3236
var accessPoint = "arn:aws:s3:" + Aws.REGION + ":" + Aws.ACCOUNT_ID + ":accesspoint/" + S3_ACCESS_POINT_NAME;
37+
38+
// Create a new S3 bucket with secure configuration including:
3339
var s3ObjectLambdaBucket = Bucket.Builder.create(this, "S3ObjectLambdaBucket")
3440
.removalPolicy(RemovalPolicy.RETAIN)
3541
.autoDeleteObjects(false)
3642
.accessControl(BucketAccessControl.BUCKET_OWNER_FULL_CONTROL)
3743
.encryption(BucketEncryption.S3_MANAGED)
3844
.blockPublicAccess(BlockPublicAccess.BLOCK_ALL)
3945
.build();
46+
47+
// Create bucket policy statement allowing access through access points
4048
var s3ObjectLambdaBucketPolicyStatement = PolicyStatement.Builder.create()
4149
.actions(List.of("*"))
4250
.principals(List.of(new AnyPrincipal()))
@@ -52,20 +60,30 @@ public S3ObjectLambdaStack(final Construct scope, final String id, final StackPr
5260
)
5361
)
5462
.build();
63+
64+
// Attach the policy to the bucket
5565
s3ObjectLambdaBucket.addToResourcePolicy(s3ObjectLambdaBucketPolicyStatement);
66+
67+
// Create the Lambda function that will transform objects
5668
var s3ObjectLambdaFunction = createS3ObjectLambdaFunction();
69+
70+
// Add permission for Lambda to write GetObject responses for s3 object
5771
var s3ObjectLambdaFunctionPolicyStatement = PolicyStatement.Builder.create()
5872
.effect(Effect.ALLOW)
5973
.resources(List.of("*"))
6074
.actions(List.of("s3-object-lambda:WriteGetObjectResponse"))
6175
.build();
6276
s3ObjectLambdaFunction.addToRolePolicy(s3ObjectLambdaFunctionPolicyStatement);
77+
78+
// Add permission for the account root to invoke the Lambda function
6379
var s3ObjectLambdaFunctionPermission = Permission.builder()
6480
.action("lambda:InvokeFunction")
6581
.principal(new AccountRootPrincipal())
6682
.sourceAccount(Aws.ACCOUNT_ID)
6783
.build();
6884
s3ObjectLambdaFunction.addPermission("S3ObjectLambdaPermission", s3ObjectLambdaFunctionPermission);
85+
86+
// Create policy allowing Lambda function to get objects through the access point
6987
var s3ObjectLambdaAccessPointPolicyStatement = PolicyStatement.Builder.create()
7088
.sid("S3ObjectLambdaAccessPointPolicyStatement")
7189
.effect(Effect.ALLOW)
@@ -76,16 +94,22 @@ public S3ObjectLambdaStack(final Construct scope, final String id, final StackPr
7694
)
7795
.resources(List.of(accessPoint + "/object/*"))
7896
.build();
97+
98+
// Create policy document containing the access point policy
7999
var s3ObjectLambdaAccessPointPolicyDocument = PolicyDocument.Builder.create()
80100
.statements(List.of(
81101
s3ObjectLambdaAccessPointPolicyStatement
82102
))
83103
.build();
104+
105+
// Create the S3 access point for direct bucket access
84106
software.amazon.awscdk.services.s3.CfnAccessPoint.Builder.create(this, "S3ObjectLambdaS3AccessPoint")
85107
.bucket(s3ObjectLambdaBucket.getBucketName())
86108
.name(S3_ACCESS_POINT_NAME)
87109
.policy(s3ObjectLambdaAccessPointPolicyDocument)
88110
.build();
111+
112+
// Create the Object Lambda access point that will transform objects
89113
var s3ObjectLambdaAccessPoint = CfnAccessPoint.Builder.create(this, "S3ObjectLambdaAccessPoint")
90114
.name(OBJECT_LAMBDA_ACCESS_POINT_NAME)
91115
.objectLambdaConfiguration(ObjectLambdaConfigurationProperty.builder()
@@ -107,28 +131,39 @@ public S3ObjectLambdaStack(final Construct scope, final String id, final StackPr
107131
)
108132
.build();
109133
CfnOutput.Builder.create(this, "s3ObjectLambdaBucketArn")
110-
.value(s3ObjectLambdaBucket.getBucketArn())
134+
.value(s3ObjectLambdaBucket.getBucketArn()) // Export bucket ARN
111135
.build();
112136
CfnOutput.Builder.create(this, "s3ObjectLambdaFunctionArn")
113-
.value(s3ObjectLambdaFunction.getFunctionArn())
137+
.value(s3ObjectLambdaFunction.getFunctionArn()) // Export Lambda function ARN
114138
.build();
115139
CfnOutput.Builder.create(this, "s3ObjectLambdaAccessPointArn")
116-
.value(s3ObjectLambdaAccessPoint.getAttrArn())
140+
.value(s3ObjectLambdaAccessPoint.getAttrArn()) // Export access point ARN
117141
.build();
142+
143+
// Create output with Console URL for easy access to the Lambda access point
118144
CfnOutput.Builder.create(this, "s3ObjectLambdaAccessPointUrl")
119145
.value("https://console.aws.amazon.com/s3/olap/" + Aws.ACCOUNT_ID + "/" + OBJECT_LAMBDA_ACCESS_POINT_NAME + "?region=" + Aws.REGION)
120146
.build();
121147
}
122148

149+
/**
150+
* Creates the Lambda function that will process S3 Object Lambda requests.
151+
* This method configures the function's runtime, code, and build process.
152+
*
153+
* @return A Lambda Function construct configured for S3 Object Lambda processing
154+
*/
123155
private Function createS3ObjectLambdaFunction() {
156+
// Define Maven packaging commands to build the Lambda function
124157
List<String> packagingInstructions = List.of(
125158
"/bin/sh",
126159
"-c",
160+
// Build the project and copy the JAR to the asset output directory
127161
"mvn -e -q clean package && cp /asset-input/target/lambda-1.0-SNAPSHOT.jar /asset-output/"
128162
);
163+
// Configure the bundling options for packaging the Lambda function
129164
var builderOptions = BundlingOptions.builder()
130-
.command(packagingInstructions)
131-
.image(Runtime.JAVA_17.getBundlingImage())
165+
.command(packagingInstructions) // Set the Maven build commands
166+
.image(Runtime.JAVA_17.getBundlingImage()) // Use Java 17 runtime image
132167
.volumes(
133168
singletonList(
134169
DockerVolume.builder()
@@ -139,10 +174,12 @@ private Function createS3ObjectLambdaFunction() {
139174
.user("root")
140175
.outputType(ARCHIVED)
141176
.build();
177+
178+
// Create the Lambda function with specified configuration
142179
return Function.Builder.create(this, "S3ObjectLambdaFunction")
143-
.runtime(Runtime.JAVA_17)
144-
.functionName("S3ObjectLambdaFunction")
145-
.memorySize(2048)
180+
.runtime(Runtime.JAVA_17) // Set Java 17 runtime
181+
.functionName("S3ObjectLambdaFunction") // Set function name
182+
.memorySize(2048) // Allocate 2GB memory
146183
.code(
147184
Code.fromAsset(
148185
"../lambda/",
@@ -152,4 +189,4 @@ private Function createS3ObjectLambdaFunction() {
152189
.handler("com.myorg.S3ObjectLambdaTransformer::handleRequest")
153190
.build();
154191
}
155-
}
192+
}

java/s3-object-lambda/lambda/src/main/java/com/myorg/S3ObjectLambdaTransformer.java

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,33 @@
2121

2222
import static java.net.http.HttpResponse.BodyHandlers.ofInputStream;
2323

24+
/**
25+
* AWS Lambda function handler that processes S3 Object Lambda requests.
26+
* This class implements the transformation logic for S3 objects accessed
27+
* through
28+
* the Object Lambda Access Point.
29+
*/
2430
public class S3ObjectLambdaTransformer {
2531

32+
/**
33+
* Main handler method that processes S3 Object Lambda events.
34+
* This method retrieves the original object from S3, applies transformations,
35+
* and returns the modified object data.
36+
*
37+
* @param event The S3 Object Lambda event containing request details
38+
* @param context The Lambda execution context
39+
*/
2640
public void handleRequest(S3ObjectLambdaEvent event, Context context) {
2741
try (var s3Client = S3Client.create()) {
42+
// Create JSON mapper and log the incoming event
2843
var objectMapper = createObjectMapper();
2944
log(context, "event: " + writeValue(objectMapper, event));
45+
46+
// Extract the pre-signed URL from the event context
3047
var objectContext = event.getGetObjectContext();
3148
var s3Url = objectContext.getInputS3Url();
49+
50+
// Create HTTP client and fetch the original object
3251
var uri = URI.create(s3Url);
3352
var httpClient = HttpClient.newBuilder().build();
3453
var httpRequest = HttpRequest.newBuilder(uri).GET().build();
@@ -39,48 +58,83 @@ public void handleRequest(S3ObjectLambdaEvent event, Context context) {
3958
.requestRoute(event.outputRoute())
4059
.requestToken(event.outputToken())
4160
.statusCode(response.statusCode());
61+
// Process successful responses (HTTP 200)
4262
if (response.statusCode() == 200) {
63+
// Build metadata object with content length and hash values
4364
var metadata = TransformedObject.Metadata.builder()
44-
.withLength((long) responseBodyBytes.length)
45-
.withMD5(DigestUtils.md5Hex(responseBodyBytes))
46-
.withSHA1(DigestUtils.sha1Hex(responseBodyBytes))
47-
.withSHA256(DigestUtils.sha256Hex(responseBodyBytes))
65+
.withLength((long) responseBodyBytes.length) // Set content length
66+
.withMD5(DigestUtils.md5Hex(responseBodyBytes)) // Calculate MD5 hash
67+
.withSHA1(DigestUtils.sha1Hex(responseBodyBytes)) // Calculate SHA1 hash
68+
.withSHA256(DigestUtils.sha256Hex(responseBodyBytes)) // Calculate SHA256 hash
4869
.build();
70+
// Create transformed object containing the metadata
4971
var transformedObject = TransformedObject.builder()
5072
.withMetadata(metadata)
5173
.build();
74+
// Log the transformed object for debugging
5275
log(context, "transformedObject: " + writeValue(objectMapper, transformedObject));
5376
requestBody = RequestBody.fromString(writeValue(objectMapper, transformedObject));
54-
} else {
77+
} else {
78+
// Handle non-200 HTTP responses by setting the error message
5579
writeGetObjectResponseRequestBuilder
56-
.errorMessage(new String(responseBodyBytes));
80+
.errorMessage(new String(responseBodyBytes)); // Convert error response body to string and set as error message
5781
}
82+
// Write the final response back to S3 Object Lambda with either transformed object or error details
5883
s3Client.writeGetObjectResponse(writeGetObjectResponseRequestBuilder.build(), requestBody);
5984
} catch (IOException | InterruptedException e) {
60-
throw new RuntimeException("Error while handling request: " + e.getMessage(), e);
61-
}
85+
// Wrap and rethrow any IO or threading exceptions that occur during processing
86+
throw new RuntimeException("Error while handling request: " + e.getMessage(), e) }
6287
}
6388

89+
/**
90+
* Reads all bytes from the input stream of an HTTP response.
91+
* Converts the response stream into a byte array for processing.
92+
*
93+
* @param response HTTP response containing the input stream to read
94+
* @return byte array containing all the data from the input stream
95+
* @throws IOException if an I/O error occurs while reading the stream
96+
*/
6497
private static byte[] readBytes(HttpResponse<InputStream> response) throws IOException {
6598
try (var inputStream = response.body()) {
6699
return inputStream.readAllBytes();
67100
}
68101
}
69102

103+
/**
104+
* Logs a message to CloudWatch using the Lambda context logger.
105+
* Prefixes the message with the request ID for tracing purposes.
106+
*
107+
* @param context Lambda execution context containing the logger
108+
* @param message Message to be logged
109+
*/
70110
private static void log(Context context, String message) {
71111
if (message != null) {
72112
Optional.ofNullable(context)
73-
.map(Context::getLogger)
74-
.ifPresent(lambdaLogger -> lambdaLogger.log(message));
113+
.map(Context::getLogger)
114+
.ifPresent(lambdaLogger -> lambdaLogger.log(message));
75115
}
76116
}
77117

118+
/**
119+
* Creates and configures a Jackson ObjectMapper for JSON serialization.
120+
* Enables pretty printing and disables failing on empty beans.
121+
*
122+
* @return Configured ObjectMapper instance
123+
*/
78124
private static ObjectMapper createObjectMapper() {
79125
var objectMapper = new ObjectMapper();
80126
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
81127
return objectMapper;
82128
}
83129

130+
/**
131+
* Serializes an object to a JSON string using the provided ObjectMapper.
132+
* Handles JsonProcessingException by returning an error message.
133+
*
134+
* @param objectMapper The ObjectMapper to use for serialization
135+
* @param object The object to serialize
136+
* @return JSON string representation of the object or error message
137+
*/
84138
private static String writeValue(ObjectMapper objectMapper, Object object) {
85139
try {
86140
return objectMapper.writeValueAsString(object);

java/s3-object-lambda/lambda/src/main/java/com/myorg/model/TransformedObject.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,19 @@
22

33
import lombok.*;
44

5+
/**
6+
* Represents a transformed S3 object with its metadata.
7+
* This class uses Lombok annotations for builder pattern, toString, and data methods generation.
8+
*/
59
@Builder(setterPrefix = "with", builderClassName = "TransformedObjectBuilder")
610
@ToString
711
@Data
812
public class TransformedObject {
913

14+
/**
15+
* Record class representing the metadata of a transformed object.
16+
* Contains various hash values and the object length for verification purposes.
17+
*/
1018
@Builder(setterPrefix = "with", builderClassName = "MetadataBuilder")
1119
public record Metadata(Long length, String MD5, String SHA1, String SHA256) {
1220

0 commit comments

Comments
 (0)