Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "feature",
"category": "S3 Event Notification",
"contributor": "",
"description": "Added `awsGeneratedTags` field to `S3Bucket` in the S3 Event Notifications module. Amazon S3 emits AWS-generated system tags on the `bucket` portion of event notifications when system tags are enabled on the source bucket. SDK consumers on the SNS/SQS/Lambda delivery paths can now access these tags via `S3Bucket#getAwsGeneratedTags()`."
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -242,7 +243,29 @@ private S3Bucket readBucket(JsonNode jsonNode) {
String name = expectStringOrNull(bucketNode, "name");
UserIdentity ownerIdentity = readOwnerIdentity(bucketNode.get("ownerIdentity"));
String arn = expectStringOrNull(bucketNode, "arn");
return new S3Bucket(name, ownerIdentity, arn);
Map<String, String> awsGeneratedTags = readStringMapOrNull(bucketNode.get("awsGeneratedTags"), "awsGeneratedTags");
return new S3Bucket(name, ownerIdentity, arn, awsGeneratedTags);
}

private Map<String, String> readStringMapOrNull(JsonNode jsonNode, String name) {
Map<String, JsonNode> mapNode = expectObjectOrNull(jsonNode, name);
if (mapNode == null) {
return null;
}
Map<String, String> result = new LinkedHashMap<>();
mapNode.forEach((tagKey, tagValueNode) ->
result.put(tagKey, readTagStringValueOrNull(name, tagKey, tagValueNode)));
return result;
}

private String readTagStringValueOrNull(String mapName, String tagKey, JsonNode tagValue) {
if (isNull(tagValue)) {
return null;
}
Validate.isTrue(tagValue.isString(),
"The value of tag '%s' in '%s' was not a string, but was: %s",
tagKey, mapName, tagValue);
return tagValue.asString();
}

private UserIdentity readOwnerIdentity(JsonNode jsonNode) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.eventnotifications.s3.model.GlacierEventData;
import software.amazon.awssdk.eventnotifications.s3.model.IntelligentTieringEventData;
Expand Down Expand Up @@ -227,6 +228,17 @@ private void writeS3Bucket(JsonWriter writer, S3Bucket bucket) {
writer.writeEndObject();
}
writeStringField(writer, "arn", bucket.getArn());
Map<String, String> awsGeneratedTags = bucket.getAwsGeneratedTags();
if (awsGeneratedTags != null && !awsGeneratedTags.isEmpty()) {
writeStringMap(writer, "awsGeneratedTags", awsGeneratedTags);
}
writer.writeEndObject();
}

private void writeStringMap(JsonWriter writer, String field, Map<String, String> map) {
writer.writeFieldName(field);
writer.writeStartObject();
map.forEach((k, v) -> writeStringField(writer, k, v));
writer.writeEndObject();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
package software.amazon.awssdk.eventnotifications.s3.model;


import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.utils.ToString;
Expand All @@ -29,11 +32,19 @@ public class S3Bucket {
private final String name;
private final UserIdentity ownerIdentity;
private final String arn;
private final Map<String, String> awsGeneratedTags;

public S3Bucket(String name, UserIdentity ownerIdentity, String arn) {
this(name, ownerIdentity, arn, null);
}

public S3Bucket(String name, UserIdentity ownerIdentity, String arn, Map<String, String> awsGeneratedTags) {
this.name = name;
this.ownerIdentity = ownerIdentity;
this.arn = arn;
this.awsGeneratedTags = awsGeneratedTags == null
? null
: Collections.unmodifiableMap(new LinkedHashMap<>(awsGeneratedTags));
}

/**
Expand All @@ -57,6 +68,14 @@ public String getArn() {
return arn;
}

/**
* @return The AWS-generated system tags for the bucket, or {@code null} if not present in the event. The returned map
* is unmodifiable.
*/
public Map<String, String> getAwsGeneratedTags() {
return awsGeneratedTags;
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand All @@ -74,23 +93,30 @@ public boolean equals(Object o) {
if (!Objects.equals(ownerIdentity, s3Bucket.ownerIdentity)) {
return false;
}
return Objects.equals(arn, s3Bucket.arn);
if (!Objects.equals(arn, s3Bucket.arn)) {
return false;
}
return Objects.equals(awsGeneratedTags, s3Bucket.awsGeneratedTags);
}

@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + (ownerIdentity != null ? ownerIdentity.hashCode() : 0);
result = 31 * result + (arn != null ? arn.hashCode() : 0);
result = 31 * result + (awsGeneratedTags != null ? awsGeneratedTags.hashCode() : 0);
return result;
}

@Override
public String toString() {
return ToString.builder("S3Bucket")
.add("name", name)
.add("ownerIdentity", ownerIdentity)
.add("arn", arn)
.build();
ToString builder = ToString.builder("S3Bucket")
.add("name", name)
.add("ownerIdentity", ownerIdentity)
.add("arn", arn);
if (awsGeneratedTags != null) {
builder.add("awsGeneratedTags", awsGeneratedTags);
}
return builder.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@

package software.amazon.awssdk.eventnotifications.s3.internal;

import nl.jqno.equalsverifier.Warning;

import nl.jqno.equalsverifier.EqualsVerifier;
import nl.jqno.equalsverifier.Warning;
import org.junit.jupiter.api.Test;

class EqualsAndHashCodeTest {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

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

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;

class S3BucketTest {

@Test
void awsGeneratedTags_nullInput_returnsNull() {
S3Bucket bucket = new S3Bucket("name", new UserIdentity("p"), "arn", null);
assertThat(bucket.getAwsGeneratedTags()).isNull();
}

@Test
void threeArgConstructor_leavesAwsGeneratedTagsNull() {
S3Bucket bucket = new S3Bucket("name", new UserIdentity("p"), "arn");
assertThat(bucket.getAwsGeneratedTags()).isNull();
}

@Test
void toString_omitsAwsGeneratedTags_whenNull() {
S3Bucket bucket = new S3Bucket("mybucket", new UserIdentity("p"), "arn:aws:s3:::mybucket");
assertThat(bucket.toString()).doesNotContain("awsGeneratedTags");
}

@Test
void toString_includesAwsGeneratedTags_whenPresent() {
Map<String, String> tags = new LinkedHashMap<>();
tags.put("aws:resource:owner", "123456789012");
tags.put("aws:resource:region", "us-west-2");

S3Bucket bucket = new S3Bucket("mybucket", new UserIdentity("p"), "arn:aws:s3:::mybucket", tags);
assertThat(bucket.toString())
.contains("awsGeneratedTags={aws:resource:owner=123456789012, aws:resource:region=us-west-2}");
}

@Test
void awsGeneratedTags_isDefensivelyCopied() {
Map<String, String> tags = new LinkedHashMap<>();
tags.put("aws:resource:owner", "123456789012");

S3Bucket bucket = new S3Bucket("mybucket", new UserIdentity("p"), "arn:aws:s3:::mybucket", tags);

// Mutating the caller's map after construction must not affect the bucket.
tags.put("aws:resource:region", "us-west-2");
tags.remove("aws:resource:owner");

assertThat(bucket.getAwsGeneratedTags())
.containsExactly(new java.util.AbstractMap.SimpleEntry<>("aws:resource:owner", "123456789012"));
}

@Test
void getAwsGeneratedTags_isUnmodifiable() {
Map<String, String> tags = new LinkedHashMap<>();
tags.put("aws:resource:owner", "123456789012");

S3Bucket bucket = new S3Bucket("mybucket", new UserIdentity("p"), "arn:aws:s3:::mybucket", tags);

Map<String, String> returned = bucket.getAwsGeneratedTags();
assertThatThrownBy(() -> returned.put("aws:resource:region", "us-west-2"))
.isInstanceOf(UnsupportedOperationException.class);
}
}
Loading
Loading