Skip to content

Commit 1bd7f6b

Browse files
committed
Add ObjectAnnotation support for S3 event notifications
Motivation: - Support ObjectAnnotation in S3 event notifications for putObjectAnnotation/deleteObjectAnnotation events, and hasObjectAnnotation on S3Object for CopyObject events. Modification: - Add S3ObjectAnnotation model class (name, size, eTag) - Add objectAnnotation list to S3 model - Add hasObjectAnnotation boolean to S3Object model - Update Reader/Writer for serialization/deserialization - Add round-trip tests for both features Result: - S3 event notifications support ObjectAnnotation data and hasObjectAnnotation flag for BYOA functionality.
1 parent f9de18c commit 1bd7f6b

7 files changed

Lines changed: 267 additions & 40 deletions

File tree

services-custom/s3-event-notifications/src/main/java/software/amazon/awssdk/eventnotifications/s3/internal/DefaultS3EventNotificationReader.java

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import software.amazon.awssdk.eventnotifications.s3.model.S3EventNotification;
3737
import software.amazon.awssdk.eventnotifications.s3.model.S3EventNotificationRecord;
3838
import software.amazon.awssdk.eventnotifications.s3.model.S3Object;
39+
import software.amazon.awssdk.eventnotifications.s3.model.S3ObjectAnnotation;
3940
import software.amazon.awssdk.eventnotifications.s3.model.TransitionEventData;
4041
import software.amazon.awssdk.eventnotifications.s3.model.UserIdentity;
4142
import software.amazon.awssdk.protocols.jsoncore.JsonNode;
@@ -213,7 +214,8 @@ private S3 readS3(JsonNode jsonNode) {
213214
S3Bucket bucket = readBucket(s3.get("bucket"));
214215
S3Object object = readObject(s3.get("object"));
215216
String s3SchemaVersion = expectStringOrNull(s3, "s3SchemaVersion");
216-
return new S3(configurationId, bucket, object, s3SchemaVersion);
217+
List<S3ObjectAnnotation> objectAnnotation = readObjectAnnotations(s3.get("objectAnnotation"));
218+
return new S3(configurationId, bucket, object, s3SchemaVersion, objectAnnotation);
217219
}
218220

219221
private S3Object readObject(JsonNode jsonNode) {
@@ -226,7 +228,10 @@ private S3Object readObject(JsonNode jsonNode) {
226228
String eTag = expectStringOrNull(objectNode, "eTag");
227229
String versionId = expectStringOrNull(objectNode, "versionId");
228230
String sequencer = expectStringOrNull(objectNode, "sequencer");
229-
return new S3Object(key, size, eTag, versionId, sequencer);
231+
JsonNode hasObjectAnnotationNode = objectNode.get("hasObjectAnnotation");
232+
Boolean hasObjectAnnotation = isNull(hasObjectAnnotationNode) ? null
233+
: expectBoolean(hasObjectAnnotationNode, "hasObjectAnnotation");
234+
return new S3Object(key, size, eTag, versionId, sequencer, hasObjectAnnotation);
230235
}
231236

232237
private S3Bucket readBucket(JsonNode jsonNode) {
@@ -312,4 +317,28 @@ private Long expectLong(JsonNode node, String name) {
312317
Validate.isTrue(node.isNumber(), "expected '%s' to be numeric, but was not", name);
313318
return Long.parseLong(node.asNumber());
314319
}
320+
321+
private List<S3ObjectAnnotation> readObjectAnnotations(JsonNode jsonNode) {
322+
List<JsonNode> annotationArray = expectArrayOrNull(jsonNode, "objectAnnotation");
323+
if (annotationArray == null) {
324+
return null;
325+
}
326+
return annotationArray.stream().map(this::readObjectAnnotation).collect(Collectors.toList());
327+
}
328+
329+
private S3ObjectAnnotation readObjectAnnotation(JsonNode jsonNode) {
330+
Map<String, JsonNode> node = expectObjectOrNull(jsonNode, "objectAnnotation[]");
331+
if (node == null) {
332+
return null;
333+
}
334+
String name = expectStringOrNull(node, "name");
335+
Long size = expectLong(node.get("size"), "size");
336+
String eTag = expectStringOrNull(node, "eTag");
337+
return new S3ObjectAnnotation(name, size, eTag);
338+
}
339+
340+
private Boolean expectBoolean(JsonNode node, String name) {
341+
Validate.isTrue(node.isBoolean(), "expected '%s' to be boolean, but was not", name);
342+
return node.asBoolean();
343+
}
315344
}

services-custom/s3-event-notifications/src/main/java/software/amazon/awssdk/eventnotifications/s3/internal/DefaultS3EventNotificationWriter.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import software.amazon.awssdk.eventnotifications.s3.model.S3EventNotification;
3333
import software.amazon.awssdk.eventnotifications.s3.model.S3EventNotificationRecord;
3434
import software.amazon.awssdk.eventnotifications.s3.model.S3Object;
35+
import software.amazon.awssdk.eventnotifications.s3.model.S3ObjectAnnotation;
3536
import software.amazon.awssdk.eventnotifications.s3.model.TransitionEventData;
3637
import software.amazon.awssdk.eventnotifications.s3.model.UserIdentity;
3738
import software.amazon.awssdk.protocols.jsoncore.JsonNodeParser;
@@ -185,6 +186,9 @@ private void writeS3(JsonWriter writer, S3 s3) {
185186
writeStringField(writer, "configurationId", s3.getConfigurationId());
186187
writeS3Bucket(writer, s3.getBucket());
187188
writeS3Object(writer, s3.getObject());
189+
if (s3.getObjectAnnotation() != null) {
190+
writeS3ObjectAnnotations(writer, s3.getObjectAnnotation());
191+
}
188192
writer.writeEndObject();
189193
}
190194

@@ -200,6 +204,9 @@ private void writeS3Object(JsonWriter writer, S3Object s3Object) {
200204
writeStringField(writer, "eTag", s3Object.getETag());
201205
writeStringField(writer, "versionId", s3Object.getVersionId());
202206
writeStringField(writer, "sequencer", s3Object.getSequencer());
207+
if (s3Object.getHasObjectAnnotation() != null) {
208+
writeBooleanField(writer, "hasObjectAnnotation", s3Object.getHasObjectAnnotation());
209+
}
203210
writer.writeEndObject();
204211
}
205212

@@ -275,6 +282,26 @@ private void writeNumericField(JsonWriter writer, String field, Long value) {
275282
}
276283
}
277284

285+
private void writeS3ObjectAnnotations(JsonWriter writer, List<S3ObjectAnnotation> annotations) {
286+
writer.writeFieldName("objectAnnotation");
287+
writer.writeStartArray();
288+
annotations.forEach(a -> writeS3ObjectAnnotation(writer, a));
289+
writer.writeEndArray();
290+
}
291+
292+
private void writeS3ObjectAnnotation(JsonWriter writer, S3ObjectAnnotation a) {
293+
writer.writeStartObject();
294+
writeStringField(writer, "name", a.getName());
295+
writeNumericField(writer, "size", a.getSize());
296+
writeStringField(writer, "eTag", a.getETag());
297+
writer.writeEndObject();
298+
}
299+
300+
private void writeBooleanField(JsonWriter writer, String field, boolean value) {
301+
writer.writeFieldName(field);
302+
writer.writeValue(value);
303+
}
304+
278305
@Override
279306
public DefaultBuilder toBuilder() {
280307
return new DefaultBuilder(this);

services-custom/s3-event-notifications/src/main/java/software/amazon/awssdk/eventnotifications/s3/model/S3.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
package software.amazon.awssdk.eventnotifications.s3.model;
1717

18+
import java.util.List;
1819
import java.util.Objects;
1920
import software.amazon.awssdk.annotations.SdkPublicApi;
2021
import software.amazon.awssdk.utils.ToString;
@@ -31,12 +32,19 @@ public class S3 {
3132
private final S3Bucket bucket;
3233
private final S3Object object;
3334
private final String s3SchemaVersion;
35+
private final List<S3ObjectAnnotation> objectAnnotation;
3436

3537
public S3(String configurationId, S3Bucket bucket, S3Object object, String s3SchemaVersion) {
38+
this(configurationId, bucket, object, s3SchemaVersion, null);
39+
}
40+
41+
public S3(String configurationId, S3Bucket bucket, S3Object object, String s3SchemaVersion,
42+
List<S3ObjectAnnotation> objectAnnotation) {
3643
this.configurationId = configurationId;
3744
this.bucket = bucket;
3845
this.object = object;
3946
this.s3SchemaVersion = s3SchemaVersion;
47+
this.objectAnnotation = objectAnnotation;
4048
}
4149

4250
/**
@@ -65,6 +73,10 @@ public String getS3SchemaVersion() {
6573
return s3SchemaVersion;
6674
}
6775

76+
public List<S3ObjectAnnotation> getObjectAnnotation() {
77+
return objectAnnotation;
78+
}
79+
6880
@Override
6981
public boolean equals(Object o) {
7082
if (this == o) {
@@ -85,7 +97,10 @@ public boolean equals(Object o) {
8597
if (!Objects.equals(object, s3.object)) {
8698
return false;
8799
}
88-
return Objects.equals(s3SchemaVersion, s3.s3SchemaVersion);
100+
if (!Objects.equals(s3SchemaVersion, s3.s3SchemaVersion)) {
101+
return false;
102+
}
103+
return Objects.equals(objectAnnotation, s3.objectAnnotation);
89104
}
90105

91106
@Override
@@ -94,6 +109,7 @@ public int hashCode() {
94109
result = 31 * result + (bucket != null ? bucket.hashCode() : 0);
95110
result = 31 * result + (object != null ? object.hashCode() : 0);
96111
result = 31 * result + (s3SchemaVersion != null ? s3SchemaVersion.hashCode() : 0);
112+
result = 31 * result + (objectAnnotation != null ? objectAnnotation.hashCode() : 0);
97113
return result;
98114
}
99115

@@ -104,6 +120,7 @@ public String toString() {
104120
.add("bucket", bucket)
105121
.add("object", object)
106122
.add("s3SchemaVersion", s3SchemaVersion)
123+
.add("objectAnnotation", objectAnnotation)
107124
.build();
108125
}
109126
}

services-custom/s3-event-notifications/src/main/java/software/amazon/awssdk/eventnotifications/s3/model/S3Object.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,19 @@ public class S3Object {
3333
private final String eTag;
3434
private final String versionId;
3535
private final String sequencer;
36+
private final Boolean hasObjectAnnotation;
3637

3738
public S3Object(String key, Long size, String eTag, String versionId, String sequencer) {
39+
this(key, size, eTag, versionId, sequencer, null);
40+
}
41+
42+
public S3Object(String key, Long size, String eTag, String versionId, String sequencer, Boolean hasObjectAnnotation) {
3843
this.key = key;
3944
this.size = size;
4045
this.eTag = eTag;
4146
this.versionId = versionId;
4247
this.sequencer = sequencer;
48+
this.hasObjectAnnotation = hasObjectAnnotation;
4349
}
4450

4551
/**
@@ -94,6 +100,16 @@ public String getSequencer() {
94100
return sequencer;
95101
}
96102

103+
/**
104+
* This field is only set on CopyObject events. Returns false when no annotations were copied,
105+
* true when annotations were copied, or null if not applicable.
106+
*
107+
* @return Boolean indicating whether object annotations were copied, or null if not applicable
108+
*/
109+
public Boolean getHasObjectAnnotation() {
110+
return hasObjectAnnotation;
111+
}
112+
97113
@Override
98114
public boolean equals(Object o) {
99115
if (this == o) {
@@ -117,7 +133,10 @@ public boolean equals(Object o) {
117133
if (!Objects.equals(versionId, s3Object.versionId)) {
118134
return false;
119135
}
120-
return Objects.equals(sequencer, s3Object.sequencer);
136+
if (!Objects.equals(sequencer, s3Object.sequencer)) {
137+
return false;
138+
}
139+
return Objects.equals(hasObjectAnnotation, s3Object.hasObjectAnnotation);
121140
}
122141

123142
@Override
@@ -127,11 +146,22 @@ public int hashCode() {
127146
result = 31 * result + (eTag != null ? eTag.hashCode() : 0);
128147
result = 31 * result + (versionId != null ? versionId.hashCode() : 0);
129148
result = 31 * result + (sequencer != null ? sequencer.hashCode() : 0);
149+
result = 31 * result + (hasObjectAnnotation != null ? hasObjectAnnotation.hashCode() : 0);
130150
return result;
131151
}
132152

133153
@Override
134154
public String toString() {
155+
if (hasObjectAnnotation != null) {
156+
return ToString.builder("S3Object")
157+
.add("key", key)
158+
.add("size", size)
159+
.add("eTag", eTag)
160+
.add("versionId", versionId)
161+
.add("sequencer", sequencer)
162+
.add("hasObjectAnnotation", hasObjectAnnotation)
163+
.build();
164+
}
135165
return ToString.builder("S3Object")
136166
.add("key", key)
137167
.add("size", size)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.eventnotifications.s3.model;
17+
18+
import java.util.Objects;
19+
import software.amazon.awssdk.annotations.SdkPublicApi;
20+
import software.amazon.awssdk.utils.ToString;
21+
22+
@SdkPublicApi
23+
public class S3ObjectAnnotation {
24+
private final String name;
25+
private final Long size;
26+
private final String eTag;
27+
28+
public S3ObjectAnnotation(String name, Long size, String eTag) {
29+
this.name = name;
30+
this.size = size;
31+
this.eTag = eTag;
32+
}
33+
34+
public String getName() {
35+
return name;
36+
}
37+
38+
public Long getSize() {
39+
return size;
40+
}
41+
42+
public String getETag() {
43+
return eTag;
44+
}
45+
46+
@Override
47+
public boolean equals(Object o) {
48+
if (this == o) {
49+
return true;
50+
}
51+
if (o == null || getClass() != o.getClass()) {
52+
return false;
53+
}
54+
S3ObjectAnnotation that = (S3ObjectAnnotation) o;
55+
if (!Objects.equals(name, that.name)) {
56+
return false;
57+
}
58+
if (!Objects.equals(size, that.size)) {
59+
return false;
60+
}
61+
return Objects.equals(eTag, that.eTag);
62+
}
63+
64+
@Override
65+
public int hashCode() {
66+
int result = name != null ? name.hashCode() : 0;
67+
result = 31 * result + (size != null ? size.hashCode() : 0);
68+
result = 31 * result + (eTag != null ? eTag.hashCode() : 0);
69+
return result;
70+
}
71+
72+
@Override
73+
public String toString() {
74+
return ToString.builder("S3ObjectAnnotation")
75+
.add("name", name)
76+
.add("size", size)
77+
.add("eTag", eTag)
78+
.build();
79+
}
80+
}

0 commit comments

Comments
 (0)