Skip to content

Commit e62e29d

Browse files
authored
Add tracking of RequestBody/ResponseTransfromer implementations used in UserAgent (#6171)
* Add useragent metadata execution attributes and apply them to useragent. Add basic tracking of requestbody/responeTransformer implementations to UA metadata. * Add delegate getter to the Notifying transformer + handle empty class names w/ unknown * Add name methods to body/response transfromer interfaces + add implementations * Checkstyle fixes + changelog * Update docs + AdditionalMetadata use builder + change all name methods to just name. * Use enum + single letter short form for RequestBody/ContentStreamProvider * Migrate to single character name + enum for remaining types * Fix test failures * Fix checkstyle, start adding tests * Add more tests * Try and fix arch test failure * Fix arch test + more test coverage * Improve test coverage * Improve regex for classWithInnerClassesToPattern * Fix minor pr issues * Fix unknown string to use enum * Fix changelog
1 parent e58ae12 commit e62e29d

File tree

48 files changed

+5779
-4856
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+5779
-4856
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "feature",
3+
"category": "AWS SDK for Java v2",
4+
"contributor": "",
5+
"description": "Add tracking of RequestBody/ResponseTransfromer implementations used in UserAgent."
6+
}

codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/specs/JsonProtocolSpec.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,10 @@ public CodeBlock executionHandler(OperationModel opModel) {
235235

236236
codeBlock.add(RequestCompressionTrait.create(opModel, model));
237237

238+
if (opModel.hasStreamingOutput()) {
239+
codeBlock.add(".withResponseTransformer(responseTransformer)");
240+
}
241+
238242
if (opModel.hasStreamingInput()) {
239243
codeBlock.add(".withRequestBody(requestBody)")
240244
.add(".withMarshaller($L)", syncStreamingMarshaller(model, opModel, marshaller));
@@ -310,6 +314,10 @@ public CodeBlock asyncExecutionHandler(IntermediateModel intermediateModel, Oper
310314
builder.add(NoneAuthTypeRequestTrait.create(opModel));
311315
}
312316

317+
if (opModel.hasStreamingOutput()) {
318+
builder.add(".withAsyncResponseTransformer(asyncResponseTransformer)");
319+
}
320+
313321
builder.add(RequestCompressionTrait.create(opModel, model))
314322
.add(".withInput($L)$L)",
315323
opModel.getInput().getVariableName(), asyncResponseTransformerVariable(isStreaming, isRestJson, opModel))

codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/specs/QueryProtocolSpec.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,9 @@ public CodeBlock executionHandler(OperationModel opModel) {
128128
}
129129

130130
codeBlock.add(RequestCompressionTrait.create(opModel, intermediateModel));
131-
131+
if (opModel.hasStreamingOutput()) {
132+
codeBlock.add(".withResponseTransformer(responseTransformer)");
133+
}
132134
if (opModel.hasStreamingInput()) {
133135
return codeBlock.add(".withRequestBody(requestBody)")
134136
.add(".withMarshaller($L));", syncStreamingMarshaller(intermediateModel, opModel, marshaller))
@@ -170,6 +172,10 @@ public CodeBlock asyncExecutionHandler(IntermediateModel intermediateModel, Oper
170172

171173
builder.add(RequestCompressionTrait.create(opModel, intermediateModel));
172174

175+
if (opModel.hasStreamingOutput()) {
176+
builder.add(".withAsyncResponseTransformer(asyncResponseTransformer)");
177+
}
178+
173179
builder.add(hostPrefixExpression(opModel) + asyncRequestBody + ".withInput($L)$L);",
174180
opModel.getInput().getVariableName(),
175181
opModel.hasStreamingOutput() ? ", asyncResponseTransformer" : "");

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/sra/test-aws-json-async-client-class.java

Lines changed: 366 additions & 365 deletions
Large diffs are not rendered by default.

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/sra/test-cbor-async-client-class.java

Lines changed: 366 additions & 365 deletions
Large diffs are not rendered by default.

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/sra/test-json-async-client-class.java

Lines changed: 408 additions & 406 deletions
Large diffs are not rendered by default.

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/sra/test-json-client-class.java

Lines changed: 300 additions & 298 deletions
Large diffs are not rendered by default.

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/sra/test-query-async-client-class.java

Lines changed: 229 additions & 227 deletions
Large diffs are not rendered by default.

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/sra/test-query-client-class.java

Lines changed: 237 additions & 236 deletions
Large diffs are not rendered by default.

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/sra/test-xml-async-client-class.java

Lines changed: 195 additions & 193 deletions
Large diffs are not rendered by default.

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/sra/test-xml-client-class.java

Lines changed: 164 additions & 163 deletions
Large diffs are not rendered by default.

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-aws-json-async-client-class.java

Lines changed: 371 additions & 370 deletions
Large diffs are not rendered by default.

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-cbor-async-client-class.java

Lines changed: 371 additions & 370 deletions
Large diffs are not rendered by default.

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-cbor-client-class.java

Lines changed: 271 additions & 270 deletions
Large diffs are not rendered by default.

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-json-async-client-class.java

Lines changed: 412 additions & 410 deletions
Large diffs are not rendered by default.

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-json-client-class.java

Lines changed: 303 additions & 301 deletions
Large diffs are not rendered by default.

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-query-async-client-class.java

Lines changed: 232 additions & 230 deletions
Large diffs are not rendered by default.

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-query-client-class.java

Lines changed: 240 additions & 239 deletions
Large diffs are not rendered by default.

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-xml-async-client-class.java

Lines changed: 198 additions & 196 deletions
Large diffs are not rendered by default.

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-xml-client-class.java

Lines changed: 167 additions & 166 deletions
Large diffs are not rendered by default.

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

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import static software.amazon.awssdk.core.interceptor.SdkExecutionAttribute.RESOLVED_CHECKSUM_SPECS;
2222
import static software.amazon.awssdk.core.internal.useragent.BusinessMetricsUtils.resolveRetryMode;
2323

24+
import java.util.ArrayList;
25+
import java.util.List;
2426
import java.util.Map;
2527
import java.util.Optional;
2628
import software.amazon.awssdk.annotations.SdkInternalApi;
@@ -36,6 +38,8 @@
3638
import software.amazon.awssdk.core.SdkRequest;
3739
import software.amazon.awssdk.core.SdkResponse;
3840
import software.amazon.awssdk.core.SelectedAuthScheme;
41+
import software.amazon.awssdk.core.async.AsyncRequestBody;
42+
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
3943
import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption;
4044
import software.amazon.awssdk.core.client.config.SdkClientConfiguration;
4145
import software.amazon.awssdk.core.client.config.SdkClientOption;
@@ -49,8 +53,11 @@
4953
import software.amazon.awssdk.core.internal.InternalCoreExecutionAttribute;
5054
import software.amazon.awssdk.core.internal.util.HttpChecksumResolver;
5155
import software.amazon.awssdk.core.signer.Signer;
56+
import software.amazon.awssdk.core.sync.ResponseTransformer;
57+
import software.amazon.awssdk.core.useragent.AdditionalMetadata;
5258
import software.amazon.awssdk.core.useragent.BusinessMetricCollection;
5359
import software.amazon.awssdk.endpoints.EndpointProvider;
60+
import software.amazon.awssdk.http.ContentStreamProvider;
5461
import software.amazon.awssdk.http.auth.scheme.NoAuthAuthScheme;
5562
import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme;
5663
import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeProvider;
@@ -159,6 +166,8 @@ private AwsExecutionContextBuilder() {
159166
signer, executionAttributes, executionAttributes.getOptionalAttribute(
160167
AwsSignerExecutionAttribute.AWS_CREDENTIALS).orElse(null)));
161168

169+
putStreamingInputOutputTypesMetadata(executionAttributes, executionParams);
170+
162171
return ExecutionContext.builder()
163172
.interceptorChain(executionInterceptorChain)
164173
.interceptorContext(interceptorContext)
@@ -168,6 +177,57 @@ private AwsExecutionContextBuilder() {
168177
.build();
169178
}
170179

180+
private static <InputT extends SdkRequest, OutputT extends SdkResponse> void putStreamingInputOutputTypesMetadata(
181+
ExecutionAttributes executionAttributes, ClientExecutionParams<InputT, OutputT> executionParams) {
182+
List<AdditionalMetadata> userAgentMetadata = new ArrayList<>();
183+
184+
if (executionParams.getRequestBody() != null) {
185+
userAgentMetadata.add(
186+
AdditionalMetadata
187+
.builder()
188+
.name("rb")
189+
.value(ContentStreamProvider.ProviderType.shortValueFromName(
190+
executionParams.getRequestBody().contentStreamProvider().name())
191+
)
192+
.build());
193+
}
194+
195+
if (executionParams.getAsyncRequestBody() != null) {
196+
userAgentMetadata.add(
197+
AdditionalMetadata
198+
.builder()
199+
.name("rb")
200+
.value(AsyncRequestBody.BodyType.shortValueFromName(
201+
executionParams.getAsyncRequestBody().body())
202+
)
203+
.build());
204+
}
205+
206+
if (executionParams.getResponseTransformer() != null) {
207+
userAgentMetadata.add(
208+
AdditionalMetadata
209+
.builder()
210+
.name("rt")
211+
.value(ResponseTransformer.TransformerType.shortValueFromName(
212+
executionParams.getResponseTransformer().name())
213+
)
214+
.build());
215+
}
216+
217+
if (executionParams.getAsyncResponseTransformer() != null) {
218+
userAgentMetadata.add(
219+
AdditionalMetadata
220+
.builder()
221+
.name("rt")
222+
.value(AsyncResponseTransformer.TransformerType.shortValueFromName(
223+
executionParams.getAsyncResponseTransformer().name())
224+
)
225+
.build());
226+
}
227+
228+
executionAttributes.putAttribute(SdkInternalExecutionAttribute.USER_AGENT_METADATA, userAgentMetadata);
229+
}
230+
171231
/**
172232
* We will load the old (non-SRA) signer if this client seems like an old version or the customer has provided a signer
173233
* override. We assume that if there's no auth schemes defined, we're on the old code path.

core/aws-core/src/test/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilderTest.java

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@
2222
import static org.mockito.Mockito.verify;
2323
import static org.mockito.Mockito.when;
2424

25+
import java.io.File;
26+
import java.io.IOException;
2527
import java.util.Arrays;
2628
import java.util.Collections;
27-
import java.util.List;
2829
import java.util.Map;
2930
import java.util.Optional;
31+
import java.util.UUID;
3032
import java.util.concurrent.CompletableFuture;
3133
import java.util.function.Supplier;
3234
import org.junit.Before;
@@ -44,6 +46,8 @@
4446
import software.amazon.awssdk.core.SdkRequest;
4547
import software.amazon.awssdk.core.SdkResponse;
4648
import software.amazon.awssdk.core.SelectedAuthScheme;
49+
import software.amazon.awssdk.core.async.AsyncRequestBody;
50+
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
4751
import software.amazon.awssdk.core.checksums.ChecksumSpecs;
4852
import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption;
4953
import software.amazon.awssdk.core.client.config.SdkClientConfiguration;
@@ -59,18 +63,19 @@
5963
import software.amazon.awssdk.core.internal.util.HttpChecksumUtils;
6064
import software.amazon.awssdk.core.signer.NoOpSigner;
6165
import software.amazon.awssdk.core.signer.Signer;
66+
import software.amazon.awssdk.core.sync.RequestBody;
67+
import software.amazon.awssdk.core.sync.ResponseTransformer;
68+
import software.amazon.awssdk.core.useragent.AdditionalMetadata;
6269
import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme;
6370
import software.amazon.awssdk.http.auth.scheme.NoAuthAuthScheme;
6471
import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme;
6572
import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption;
6673
import software.amazon.awssdk.http.auth.spi.signer.HttpSigner;
67-
import software.amazon.awssdk.http.auth.spi.signer.SignerProperty;
6874
import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity;
6975
import software.amazon.awssdk.identity.spi.IdentityProvider;
7076
import software.amazon.awssdk.identity.spi.IdentityProviders;
7177
import software.amazon.awssdk.identity.spi.TokenIdentity;
7278
import software.amazon.awssdk.profiles.ProfileFile;
73-
import software.amazon.awssdk.regions.RegionScope;
7479

7580
@RunWith(MockitoJUnitRunner.class)
7681
public class AwsExecutionContextBuilderTest {
@@ -437,6 +442,74 @@ public void invokeInterceptorsAndCreateExecutionContext_requestOverrideForIdenti
437442
assertThat(actualTokenProvider).isSameAs(requestTokenProvider);
438443
}
439444

445+
@Test
446+
public void invokeInterceptorsAndCreateExecutionContext_withRequestBody_addsUserAgentMetadata() throws IOException {
447+
ClientExecutionParams<SdkRequest, SdkResponse> executionParams = clientExecutionParams();
448+
File testFile = File.createTempFile("testFile", UUID.randomUUID().toString());
449+
testFile.deleteOnExit();
450+
executionParams.withRequestBody(RequestBody.fromFile(testFile));
451+
452+
ExecutionContext executionContext =
453+
AwsExecutionContextBuilder.invokeInterceptorsAndCreateExecutionContext(executionParams,
454+
testClientConfiguration().build());
455+
456+
ExecutionAttributes executionAttributes = executionContext.executionAttributes();
457+
assertThat(executionAttributes.getAttribute(SdkInternalExecutionAttribute.USER_AGENT_METADATA)).isEqualTo(
458+
Collections.singletonList(AdditionalMetadata.builder().name("rb").value("f").build())
459+
);
460+
}
461+
462+
@Test
463+
public void invokeInterceptorsAndCreateExecutionContext_withResponseTransformer_addsUserAgentMetadata() throws IOException {
464+
ClientExecutionParams<SdkRequest, SdkResponse> executionParams = clientExecutionParams();
465+
File testFile = File.createTempFile("testFile", UUID.randomUUID().toString());
466+
testFile.deleteOnExit();
467+
executionParams.withResponseTransformer(ResponseTransformer.toFile(testFile));
468+
469+
ExecutionContext executionContext =
470+
AwsExecutionContextBuilder.invokeInterceptorsAndCreateExecutionContext(executionParams,
471+
testClientConfiguration().build());
472+
473+
ExecutionAttributes executionAttributes = executionContext.executionAttributes();
474+
assertThat(executionAttributes.getAttribute(SdkInternalExecutionAttribute.USER_AGENT_METADATA)).isEqualTo(
475+
Collections.singletonList(AdditionalMetadata.builder().name("rt").value("f").build())
476+
);
477+
}
478+
479+
@Test
480+
public void invokeInterceptorsAndCreateExecutionContext_withAsyncRequestBody_addsUserAgentMetadata() throws IOException {
481+
ClientExecutionParams<SdkRequest, SdkResponse> executionParams = clientExecutionParams();
482+
File testFile = File.createTempFile("testFile", UUID.randomUUID().toString());
483+
testFile.deleteOnExit();
484+
executionParams.withAsyncRequestBody(AsyncRequestBody.fromFile(testFile));
485+
486+
ExecutionContext executionContext =
487+
AwsExecutionContextBuilder.invokeInterceptorsAndCreateExecutionContext(executionParams,
488+
testClientConfiguration().build());
489+
490+
ExecutionAttributes executionAttributes = executionContext.executionAttributes();
491+
assertThat(executionAttributes.getAttribute(SdkInternalExecutionAttribute.USER_AGENT_METADATA)).isEqualTo(
492+
Collections.singletonList(AdditionalMetadata.builder().name("rb").value("f").build())
493+
);
494+
}
495+
496+
@Test
497+
public void invokeInterceptorsAndCreateExecutionContext_withAsyncResponseTransformer_addsUserAgentMetadata() throws IOException {
498+
ClientExecutionParams<SdkRequest, SdkResponse> executionParams = clientExecutionParams();
499+
File testFile = File.createTempFile("testFile", UUID.randomUUID().toString());
500+
testFile.deleteOnExit();
501+
executionParams.withAsyncResponseTransformer(AsyncResponseTransformer.toFile(testFile));
502+
503+
ExecutionContext executionContext =
504+
AwsExecutionContextBuilder.invokeInterceptorsAndCreateExecutionContext(executionParams,
505+
testClientConfiguration().build());
506+
507+
ExecutionAttributes executionAttributes = executionContext.executionAttributes();
508+
assertThat(executionAttributes.getAttribute(SdkInternalExecutionAttribute.USER_AGENT_METADATA)).isEqualTo(
509+
Collections.singletonList(AdditionalMetadata.builder().name("rt").value("f").build())
510+
);
511+
}
512+
440513
private ClientExecutionParams<SdkRequest, SdkResponse> clientExecutionParams() {
441514
return clientExecutionParams(sdkRequest);
442515
}

core/sdk-core/src/main/java/software/amazon/awssdk/core/async/AsyncRequestBody.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@
2323
import java.nio.charset.StandardCharsets;
2424
import java.nio.file.Path;
2525
import java.util.Arrays;
26+
import java.util.Map;
2627
import java.util.Optional;
2728
import java.util.concurrent.ExecutorService;
2829
import java.util.function.Consumer;
2930
import org.reactivestreams.Publisher;
3031
import org.reactivestreams.Subscriber;
32+
import software.amazon.awssdk.annotations.SdkProtectedApi;
3133
import software.amazon.awssdk.annotations.SdkPublicApi;
3234
import software.amazon.awssdk.core.FileRequestBodyConfiguration;
3335
import software.amazon.awssdk.core.internal.async.ByteBuffersAsyncRequestBody;
@@ -37,6 +39,7 @@
3739
import software.amazon.awssdk.core.internal.util.Mimetype;
3840
import software.amazon.awssdk.utils.BinaryUtils;
3941
import software.amazon.awssdk.utils.Validate;
42+
import software.amazon.awssdk.utils.internal.EnumUtils;
4043

4144
/**
4245
* Interface to allow non-blocking streaming of request content. This follows the reactive streams pattern where this interface is
@@ -74,6 +77,16 @@ default String contentType() {
7477
return Mimetype.MIMETYPE_OCTET_STREAM;
7578
}
7679

80+
/**
81+
* Each AsyncRequestBody should return a well-formed name that can be used to identify the implementation.
82+
* The body name should only include alphanumeric characters.
83+
*
84+
* @return String containing the identifying name of this AsyncRequestBody implementation.
85+
*/
86+
default String body() {
87+
return BodyType.UNKNOWN.getName();
88+
}
89+
7790
/**
7891
* Creates an {@link AsyncRequestBody} the produces data from the input ByteBuffer publisher. The data is delivered when the
7992
* publisher publishes the data.
@@ -96,6 +109,11 @@ public Optional<Long> contentLength() {
96109
public void subscribe(Subscriber<? super ByteBuffer> s) {
97110
publisher.subscribe(s);
98111
}
112+
113+
@Override
114+
public String body() {
115+
return BodyType.PUBLISHER.getName();
116+
}
99117
};
100118
}
101119

@@ -513,4 +531,36 @@ default SdkPublisher<AsyncRequestBody> split(Consumer<AsyncRequestBodySplitConfi
513531
Validate.notNull(splitConfiguration, "splitConfiguration");
514532
return split(AsyncRequestBodySplitConfiguration.builder().applyMutation(splitConfiguration).build());
515533
}
534+
535+
@SdkProtectedApi
536+
enum BodyType {
537+
FILE("File", "f"),
538+
BYTES("Bytes", "b"),
539+
STREAM("Stream", "s"),
540+
PUBLISHER("Publisher", "p"),
541+
UNKNOWN("Unknown", "u");
542+
543+
private static final Map<String, BodyType> VALUE_MAP =
544+
EnumUtils.uniqueIndex(BodyType.class, BodyType::getName);
545+
546+
private final String name;
547+
private final String shortValue;
548+
549+
BodyType(String name, String shortValue) {
550+
this.name = name;
551+
this.shortValue = shortValue;
552+
}
553+
554+
public String getName() {
555+
return name;
556+
}
557+
558+
public String getShortValue() {
559+
return shortValue;
560+
}
561+
562+
public static String shortValueFromName(String name) {
563+
return VALUE_MAP.getOrDefault(name, UNKNOWN).getShortValue();
564+
}
565+
}
516566
}

0 commit comments

Comments
 (0)