Skip to content

Commit 01b619d

Browse files
Prevent duplicate ExecutionAttribute names and fix name for FIPS_ENDPOINT_ENABLED attribute (#2968)
ExecutionAttributes are used as a key for looking up values in ExecutionAttributes maps. Having two attributes with the same name would be harmful since it may unintentionally override another attribute in the map, or allow an attribute lookup to accidentally reference another attribute's value. ExecutionAttributes are conventionally referenced via static constants. We can leverage this convention to ensure that no two attributes are ever created with the same name. Note: This has the possibility to be a breaking change if, and only if, users are creating ExecutionAttributes rather than referencing ones that already exist. Such a scenario seems highly unlikely since it breaks the clear convention of referencing these as static constants, and because ExecutionAttributes are primarily used internally within the SDK.
1 parent 211d214 commit 01b619d

File tree

8 files changed

+116
-24
lines changed

8 files changed

+116
-24
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"category": "AWS SDK for Java v2",
3+
"contributor": "",
4+
"type": "bugfix",
5+
"description": "Prevent duplicate ExecutionAttribute names and fix name for FIPS_ENDPOINT_ENABLED attribute"
6+
}

core/aws-core/src/main/java/software/amazon/awssdk/awscore/AwsExecutionAttribute.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public final class AwsExecutionAttribute extends SdkExecutionAttribute {
4949
* Whether fips endpoints were enabled for this request.
5050
*/
5151
public static final ExecutionAttribute<Boolean> FIPS_ENDPOINT_ENABLED =
52-
new ExecutionAttribute<>("DualstackEndpointsEnabled");
52+
new ExecutionAttribute<>("FipsEndpointsEnabled");
5353

5454
private AwsExecutionAttribute() {
5555
}

core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/ExecutionAttribute.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
package software.amazon.awssdk.core.interceptor;
1717

1818
import java.util.Objects;
19+
import java.util.concurrent.ConcurrentHashMap;
20+
import java.util.concurrent.ConcurrentMap;
1921
import software.amazon.awssdk.annotations.SdkPublicApi;
2022

2123
/**
@@ -42,7 +44,8 @@
4244
*/
4345
@SdkPublicApi
4446
public final class ExecutionAttribute<T> {
45-
47+
private static final ConcurrentMap<String, ExecutionAttribute<?>> NAME_HISTORY = new ConcurrentHashMap<>();
48+
4649
private final String name;
4750

4851
/**
@@ -52,6 +55,20 @@ public final class ExecutionAttribute<T> {
5255
*/
5356
public ExecutionAttribute(String name) {
5457
this.name = name;
58+
ensureUnique();
59+
}
60+
61+
private void ensureUnique() {
62+
ExecutionAttribute<?> prev = NAME_HISTORY.putIfAbsent(name, this);
63+
if (prev != null) {
64+
throw new IllegalArgumentException(String.format("No duplicate ExecutionAttribute names allowed but both "
65+
+ "ExecutionAttributes %s and %s have the same name: %s. "
66+
+ "ExecutionAttributes should be referenced from a shared static "
67+
+ "constant to protect against erroneous or unexpected collisions.",
68+
Integer.toHexString(System.identityHashCode(prev)),
69+
Integer.toHexString(System.identityHashCode(this)),
70+
name));
71+
}
5572
}
5673

5774
@Override

core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/ExecutionAttributes.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.Map;
2121
import software.amazon.awssdk.annotations.NotThreadSafe;
2222
import software.amazon.awssdk.annotations.SdkPublicApi;
23+
import software.amazon.awssdk.utils.ToString;
2324
import software.amazon.awssdk.utils.Validate;
2425
import software.amazon.awssdk.utils.builder.CopyableBuilder;
2526
import software.amazon.awssdk.utils.builder.ToCopyableBuilder;
@@ -126,6 +127,13 @@ public int hashCode() {
126127
return attributes != null ? attributes.hashCode() : 0;
127128
}
128129

130+
@Override
131+
public String toString() {
132+
return ToString.builder("ExecutionAttributes")
133+
.add("attributes", attributes.keySet())
134+
.build();
135+
}
136+
129137
public static ExecutionAttributes unmodifiableExecutionAttributes(ExecutionAttributes attributes) {
130138
return new UnmodifiableExecutionAttributes(attributes);
131139
}

core/sdk-core/src/test/java/software/amazon/awssdk/core/RequestOverrideConfigurationTest.java

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import java.util.HashMap;
2727
import java.util.List;
2828
import java.util.Map;
29+
import java.util.concurrent.ConcurrentHashMap;
30+
import java.util.concurrent.ConcurrentMap;
2931
import nl.jqno.equalsverifier.EqualsVerifier;
3032
import org.junit.jupiter.api.Test;
3133
import software.amazon.awssdk.core.interceptor.ExecutionAttribute;
@@ -36,6 +38,13 @@
3638
import software.amazon.awssdk.utils.ImmutableMap;
3739

3840
public class RequestOverrideConfigurationTest {
41+
private static final ConcurrentMap<String, ExecutionAttribute<Object>> ATTR_POOL = new ConcurrentHashMap<>();
42+
43+
private static ExecutionAttribute<Object> attr(String name) {
44+
name = RequestOverrideConfigurationTest.class.getName() + ":" + name;
45+
return ATTR_POOL.computeIfAbsent(name, ExecutionAttribute::new);
46+
}
47+
3948
private static final String HEADER = "header";
4049
private static final String QUERY_PARAM = "queryparam";
4150

@@ -56,7 +65,7 @@ public void toBuilder_minimal() {
5665

5766
@Test
5867
public void toBuilder_maximal() {
59-
ExecutionAttribute testAttribute = new ExecutionAttribute("TestAttribute");
68+
ExecutionAttribute testAttribute = attr("TestAttribute");
6069
String expectedValue = "Value1";
6170

6271
RequestOverrideConfiguration configuration = SdkRequestOverrideConfiguration.builder()
@@ -210,7 +219,7 @@ public void addMetricPublisher_listPreviouslyAdded_appendedToList() {
210219
public void executionAttributes_createsCopy() {
211220
ExecutionAttributes executionAttributes = new ExecutionAttributes();
212221

213-
ExecutionAttribute testAttribute = new ExecutionAttribute("TestAttribute");
222+
ExecutionAttribute testAttribute = attr("TestAttribute");
214223
String expectedValue = "Value1";
215224
executionAttributes.putAttribute(testAttribute, expectedValue);
216225

@@ -226,7 +235,7 @@ public void executionAttributes_createsCopy() {
226235
@Test
227236
public void executionAttributes_isImmutable() {
228237
ExecutionAttributes executionAttributes = new ExecutionAttributes();
229-
ExecutionAttribute testAttribute = new ExecutionAttribute("TestAttribute");
238+
ExecutionAttribute testAttribute = attr("TestAttribute");
230239
String expectedValue = "Value1";
231240
executionAttributes.putAttribute(testAttribute, expectedValue);
232241

@@ -252,7 +261,7 @@ public void executionAttributes_isImmutable() {
252261
public void executionAttributes_maintainsAllAdded() {
253262
Map<ExecutionAttribute, Object> executionAttributeObjectMap = new HashMap<>();
254263
for (int i = 0; i < 5; i++) {
255-
executionAttributeObjectMap.put(new ExecutionAttribute<>("Attribute" + i), mock(Object.class));
264+
executionAttributeObjectMap.put(attr("Attribute" + i), mock(Object.class));
256265
}
257266

258267
SdkRequestOverrideConfiguration.Builder builder = SdkRequestOverrideConfiguration.builder();
@@ -269,12 +278,12 @@ public void executionAttributes_maintainsAllAdded() {
269278
public void executionAttributes_overwritesPreviouslyAdded() {
270279
ExecutionAttributes executionAttributes = new ExecutionAttributes();
271280
for (int i = 0; i < 5; i++) {
272-
executionAttributes.putAttribute(new ExecutionAttribute<>("Attribute" + i), mock(Object.class));
281+
executionAttributes.putAttribute(attr("Attribute" + i), mock(Object.class));
273282
}
274283

275284
SdkRequestOverrideConfiguration.Builder builder = SdkRequestOverrideConfiguration.builder();
276285

277-
builder.putExecutionAttribute(new ExecutionAttribute("AddedAttribute"), mock(Object.class));
286+
builder.putExecutionAttribute(attr("AddedAttribute"), mock(Object.class));
278287
builder.executionAttributes(executionAttributes);
279288
SdkRequestOverrideConfiguration overrideConfig = builder.build();
280289
assertThat(overrideConfig.executionAttributes().getAttributes()).isEqualTo(executionAttributes.getAttributes());
@@ -284,13 +293,13 @@ public void executionAttributes_overwritesPreviouslyAdded() {
284293
public void executionAttributes_listPreviouslyAdded_appendedToList() {
285294
ExecutionAttributes executionAttributes = new ExecutionAttributes();
286295
for (int i = 0; i < 5; i++) {
287-
executionAttributes.putAttribute(new ExecutionAttribute<>("Attribute" + i), mock(Object.class));
296+
executionAttributes.putAttribute(attr("Attribute" + i), mock(Object.class));
288297
}
289298

290299
SdkRequestOverrideConfiguration.Builder builder = SdkRequestOverrideConfiguration.builder();
291300

292301
builder.executionAttributes(executionAttributes);
293-
ExecutionAttribute addedAttribute = new ExecutionAttribute("AddedAttribute");
302+
ExecutionAttribute addedAttribute = attr("AddedAttribute");
294303
Object addedValue = mock(Object.class);
295304

296305
builder.putExecutionAttribute(addedAttribute, addedValue);
@@ -303,7 +312,7 @@ public void executionAttributes_listPreviouslyAdded_appendedToList() {
303312
public void testConfigurationEquals() {
304313
ExecutionAttributes executionAttributes = new ExecutionAttributes();
305314
for (int i = 0; i < 5; i++) {
306-
executionAttributes.putAttribute(new ExecutionAttribute<>("Attribute" + i), mock(Object.class));
315+
executionAttributes.putAttribute(attr("Attribute" + i), mock(Object.class));
307316
}
308317

309318
SdkRequestOverrideConfiguration request1Override = SdkRequestOverrideConfiguration.builder()

core/sdk-core/src/test/java/software/amazon/awssdk/core/client/config/ClientOverrideConfigurationTest.java

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import java.util.HashMap;
2828
import java.util.List;
2929
import java.util.Map;
30+
import java.util.concurrent.ConcurrentHashMap;
31+
import java.util.concurrent.ConcurrentMap;
3032
import org.junit.jupiter.api.Test;
3133
import software.amazon.awssdk.core.interceptor.ExecutionAttribute;
3234
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
@@ -36,6 +38,12 @@
3638
import software.amazon.awssdk.utils.ImmutableMap;
3739

3840
public class ClientOverrideConfigurationTest {
41+
private static final ConcurrentMap<String, ExecutionAttribute<Object>> ATTR_POOL = new ConcurrentHashMap<>();
42+
43+
private static ExecutionAttribute<Object> attr(String name) {
44+
name = ClientOverrideConfigurationTest.class.getName() + ":" + name;
45+
return ATTR_POOL.computeIfAbsent(name, ExecutionAttribute::new);
46+
}
3947

4048
@Test
4149
public void addingSameItemTwice_shouldOverride() {
@@ -188,7 +196,7 @@ public void addMetricPublisher_listPreviouslyAdded_appendedToList() {
188196
public void executionAttributes_createsCopy() {
189197
ExecutionAttributes executionAttributes = new ExecutionAttributes();
190198

191-
ExecutionAttribute testAttribute = new ExecutionAttribute("TestAttribute");
199+
ExecutionAttribute testAttribute = attr("TestAttribute");
192200
String expectedValue = "Value1";
193201
executionAttributes.putAttribute(testAttribute, expectedValue);
194202

@@ -204,7 +212,7 @@ public void executionAttributes_createsCopy() {
204212
public void executionAttributes_isImmutable() {
205213
ExecutionAttributes executionAttributes = new ExecutionAttributes();
206214

207-
ExecutionAttribute testAttribute = new ExecutionAttribute("TestAttribute");
215+
ExecutionAttribute testAttribute = attr("TestAttribute");
208216
String expectedValue = "Value1";
209217
executionAttributes.putAttribute(testAttribute, expectedValue);
210218

@@ -224,7 +232,7 @@ public void executionAttributes_isImmutable() {
224232
public void executionAttributes_maintainsAllAdded() {
225233
Map<ExecutionAttribute, Object> executionAttributeObjectMap = new HashMap<>();
226234
for (int i = 0; i < 5; i++) {
227-
executionAttributeObjectMap.put(new ExecutionAttribute<>("Attribute" + i), mock(Object.class));
235+
executionAttributeObjectMap.put(attr("Attribute" + i), mock(Object.class));
228236
}
229237

230238
ClientOverrideConfiguration.Builder builder = ClientOverrideConfiguration.builder();
@@ -241,12 +249,12 @@ public void executionAttributes_maintainsAllAdded() {
241249
public void executionAttributes_overwritesPreviouslyAdded() {
242250
ExecutionAttributes executionAttributes = new ExecutionAttributes();
243251
for (int i = 0; i < 5; i++) {
244-
executionAttributes.putAttribute(new ExecutionAttribute<>("Attribute" + i), mock(Object.class));
252+
executionAttributes.putAttribute(attr("Attribute" + i), mock(Object.class));
245253
}
246254

247255
ClientOverrideConfiguration.Builder builder = ClientOverrideConfiguration.builder();
248256

249-
builder.putExecutionAttribute(new ExecutionAttribute("AddedAttribute"), mock(Object.class));
257+
builder.putExecutionAttribute(attr("AddedAttribute"), mock(Object.class));
250258
builder.executionAttributes(executionAttributes);
251259
ClientOverrideConfiguration overrideConfig = builder.build();
252260
assertThat(overrideConfig.executionAttributes().getAttributes()).isEqualTo(executionAttributes.getAttributes());
@@ -256,13 +264,13 @@ public void executionAttributes_overwritesPreviouslyAdded() {
256264
public void executionAttributes_listPreviouslyAdded_appendedToList() {
257265
ExecutionAttributes executionAttributes = new ExecutionAttributes();
258266
for (int i = 0; i < 5; i++) {
259-
executionAttributes.putAttribute(new ExecutionAttribute<>("Attribute" + i), mock(Object.class));
267+
executionAttributes.putAttribute(attr("Attribute" + i), mock(Object.class));
260268
}
261269

262270
ClientOverrideConfiguration.Builder builder = ClientOverrideConfiguration.builder();
263271

264272
builder.executionAttributes(executionAttributes);
265-
ExecutionAttribute addedAttribute = new ExecutionAttribute("AddedAttribute");
273+
ExecutionAttribute addedAttribute = attr("AddedAttribute");
266274
Object addedValue = mock(Object.class);
267275

268276
builder.putExecutionAttribute(addedAttribute, addedValue);
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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.core.interceptor;
17+
18+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
19+
20+
import org.junit.jupiter.api.DisplayName;
21+
import org.junit.jupiter.api.Test;
22+
23+
class ExecutionAttributeTest {
24+
25+
@Test
26+
@DisplayName("Ensure that two different ExecutionAttributes are not allowed to have the same name (and hash key)")
27+
void testUniqueness() {
28+
String name = "ExecutionAttributeTest";
29+
ExecutionAttribute<Integer> first = new ExecutionAttribute<>(name);
30+
assertThatThrownBy(() -> {
31+
ExecutionAttribute<Integer> second = new ExecutionAttribute<>(name);
32+
}).isInstanceOf(IllegalArgumentException.class)
33+
.hasMessageContaining(name);
34+
}
35+
}

test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/ExecutionAttributesTest.java

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import static org.mockito.Mockito.mock;
77

88
import java.util.concurrent.CompletionException;
9+
import java.util.concurrent.ConcurrentHashMap;
10+
import java.util.concurrent.ConcurrentMap;
911
import org.junit.Rule;
1012
import org.junit.Test;
1113
import org.junit.rules.ExpectedException;
@@ -24,6 +26,13 @@
2426
import software.amazon.awssdk.services.protocolrestjson.model.AllTypesRequest;
2527

2628
public class ExecutionAttributesTest {
29+
private static final ConcurrentMap<String, ExecutionAttribute<Object>> ATTR_POOL = new ConcurrentHashMap<>();
30+
31+
private static ExecutionAttribute<Object> attr(String name) {
32+
name = ExecutionAttributesTest.class.getName() + ":" + name;
33+
return ATTR_POOL.computeIfAbsent(name, ExecutionAttribute::new);
34+
}
35+
2736
@Rule
2837
public ExpectedException thrown = ExpectedException.none();
2938

@@ -78,7 +87,7 @@ public void asyncClient_clientOverrideExecutionAttribute() {
7887
ExecutionInterceptor interceptor = mock(ExecutionInterceptor.class);
7988
ArgumentCaptor<ExecutionAttributes> attributesCaptor = ArgumentCaptor.forClass(ExecutionAttributes.class);
8089
doThrow(new RuntimeException("BOOM")).when(interceptor).beforeExecution(any(Context.BeforeExecution.class), attributesCaptor.capture());
81-
ExecutionAttribute testAttribute = new ExecutionAttribute<>("TestAttribute");
90+
ExecutionAttribute testAttribute = attr("TestAttribute");
8291
String testValue = "TestValue";
8392

8493
ProtocolRestJsonAsyncClient async = ProtocolRestJsonAsyncClient.builder()
@@ -103,7 +112,7 @@ public void asyncClient_requestOverrideExecutionAttribute() {
103112
ExecutionInterceptor interceptor = mock(ExecutionInterceptor.class);
104113
ArgumentCaptor<ExecutionAttributes> attributesCaptor = ArgumentCaptor.forClass(ExecutionAttributes.class);
105114
doThrow(new RuntimeException("BOOM")).when(interceptor).beforeExecution(any(Context.BeforeExecution.class), attributesCaptor.capture());
106-
ExecutionAttribute testAttribute = new ExecutionAttribute<>("TestAttribute");
115+
ExecutionAttribute testAttribute = attr("TestAttribute");
107116
String testValue = "TestValue";
108117

109118
ProtocolRestJsonAsyncClient async = ProtocolRestJsonAsyncClient.builder()
@@ -131,7 +140,7 @@ public void asyncClient_requestOverrideExecutionAttributesHavePrecedence() {
131140
ExecutionInterceptor interceptor = mock(ExecutionInterceptor.class);
132141
ArgumentCaptor<ExecutionAttributes> attributesCaptor = ArgumentCaptor.forClass(ExecutionAttributes.class);
133142
doThrow(new RuntimeException("BOOM")).when(interceptor).beforeExecution(any(Context.BeforeExecution.class), attributesCaptor.capture());
134-
ExecutionAttribute testAttribute = new ExecutionAttribute<>("TestAttribute");
143+
ExecutionAttribute testAttribute = attr("TestAttribute");
135144
String testValue = "TestValue";
136145

137146
ProtocolRestJsonAsyncClient async = ProtocolRestJsonAsyncClient.builder()
@@ -160,7 +169,7 @@ public void syncClient_requestOverrideExecutionAttribute() {
160169
ExecutionInterceptor interceptor = mock(ExecutionInterceptor.class);
161170
ArgumentCaptor<ExecutionAttributes> attributesCaptor = ArgumentCaptor.forClass(ExecutionAttributes.class);
162171
doThrow(new RuntimeException("BOOM")).when(interceptor).beforeExecution(any(Context.BeforeExecution.class), attributesCaptor.capture());
163-
ExecutionAttribute testAttribute = new ExecutionAttribute<>("TestAttribute");
172+
ExecutionAttribute testAttribute = attr("TestAttribute");
164173
String testValue = "TestValue";
165174

166175
ProtocolRestJsonClient sync = ProtocolRestJsonClient.builder()
@@ -188,7 +197,7 @@ public void syncClient_requestOverrideExecutionAttributesHavePrecedence() {
188197
ExecutionInterceptor interceptor = mock(ExecutionInterceptor.class);
189198
ArgumentCaptor<ExecutionAttributes> attributesCaptor = ArgumentCaptor.forClass(ExecutionAttributes.class);
190199
doThrow(new RuntimeException("BOOM")).when(interceptor).beforeExecution(any(Context.BeforeExecution.class), attributesCaptor.capture());
191-
ExecutionAttribute testAttribute = new ExecutionAttribute<>("TestAttribute");
200+
ExecutionAttribute testAttribute = attr("TestAttribute");
192201
String testValue = "TestValue";
193202

194203
ProtocolRestJsonClient sync = ProtocolRestJsonClient.builder()
@@ -254,6 +263,6 @@ public void testAttributeEquals() {
254263
}
255264

256265
private ExecutionAttribute getAttribute(int i) {
257-
return new ExecutionAttribute<>("TestAttribute" + i);
266+
return attr("TestAttribute" + i);
258267
}
259268
}

0 commit comments

Comments
 (0)