Skip to content

Commit 43fed73

Browse files
authored
fix(CopySourceRequestInS3): Updated codegen to customize the request before calling service (#5244)
* fix(CopySourceRequestInS3): Updated codegen to customize the request before sending, instead of using interceptors. This change ensures that customized parameters are available for EndpointResolveInterceptors during beforeExecution. * Handle review comments * Handle review comments 2 * Handle review comments 3 * Added change log
1 parent a62ea9b commit 43fed73

File tree

16 files changed

+416
-56
lines changed

16 files changed

+416
-56
lines changed

Diff for: .changes/next-release/bugfix-AmazonS3-ca102ca.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "bugfix",
3+
"category": "Amazon S3",
4+
"contributor": "",
5+
"description": "Remove CopySourceInterceptor and add the updation of \"copySource\" parameter in CopyObject and UploadPartCopy API via preClientExecutionRequestCustomizer Customization before client execution."
6+
}

Diff for: codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/CustomizationConfig.java

+19
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import software.amazon.awssdk.codegen.model.rules.endpoints.ParameterModel;
2424
import software.amazon.awssdk.codegen.model.service.ClientContextParam;
2525
import software.amazon.awssdk.codegen.model.service.CustomOperationContextParam;
26+
import software.amazon.awssdk.codegen.model.service.PreClientExecutionRequestCustomizer;
2627
import software.amazon.awssdk.core.retry.RetryMode;
2728
import software.amazon.awssdk.core.traits.PayloadTrait;
2829
import software.amazon.awssdk.utils.AttributeMap;
@@ -328,6 +329,14 @@ public class CustomizationConfig {
328329

329330
private List<CustomOperationContextParam> customOperationContextParams;
330331

332+
/**
333+
* A map that associates API names with their respective custom request transformers.
334+
* The {@link PreClientExecutionRequestCustomizer} allows for dynamic and specific handling of API requests,
335+
* ensuring that each request that requires custom handling can be appropriately transformed based on its corresponding
336+
* API name.
337+
*/
338+
private Map<String, PreClientExecutionRequestCustomizer> preClientExecutionRequestCustomizer;
339+
331340
private CustomizationConfig() {
332341
}
333342

@@ -869,4 +878,14 @@ public List<CustomOperationContextParam> getCustomOperationContextParams() {
869878
public void setCustomOperationContextParams(List<CustomOperationContextParam> customOperationContextParams) {
870879
this.customOperationContextParams = customOperationContextParams;
871880
}
881+
882+
public Map<String, PreClientExecutionRequestCustomizer> getPreClientExecutionRequestCustomizer() {
883+
return preClientExecutionRequestCustomizer;
884+
}
885+
886+
public void setPreClientExecutionRequestCustomizer(Map<String, PreClientExecutionRequestCustomizer>
887+
preClientExecutionRequestCustomizer) {
888+
this.preClientExecutionRequestCustomizer = preClientExecutionRequestCustomizer;
889+
}
890+
872891
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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.codegen.model.service;
17+
18+
/**
19+
* Represents a custom request transformer for API requests.
20+
*
21+
* <p>This class allows for dynamic and specific transformation of API requests,
22+
* ensuring that each request is appropriately transformed based on the
23+
* transformation logic defined in the specified {@link PreClientExecutionRequestCustomizer#getClassName()} and
24+
* {@link PreClientExecutionRequestCustomizer#getMethodName()}.
25+
*
26+
* <p>Example:
27+
* <pre>
28+
* {
29+
* "methodName": "dummyRequestModifier",
30+
* "className": "software.amazon.awssdk.codegen.internal.UtilsTest"
31+
* }
32+
* </pre>
33+
*
34+
* <p>The class should have a public static method dummyRequestModifier
35+
* that takes an input and returns an output of ApiRequest for which Customization is applied.
36+
*/
37+
38+
public class PreClientExecutionRequestCustomizer {
39+
40+
/**
41+
* The fully qualified name of the class that defines the transformation method. The {@code methodName} is the
42+
*/
43+
private String className;
44+
45+
/**
46+
* The name of the method within that class which will perform the transformation
47+
*/
48+
private String methodName;
49+
50+
public String getClassName() {
51+
return className;
52+
}
53+
54+
public void setClassName(String className) {
55+
this.className = className;
56+
}
57+
58+
public String getMethodName() {
59+
return methodName;
60+
}
61+
62+
public void setMethodName(String methodName) {
63+
this.methodName = methodName;
64+
}
65+
}

Diff for: codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/AsyncClientClass.java

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import static software.amazon.awssdk.codegen.internal.Constant.EVENT_PUBLISHER_PARAM_NAME;
2626
import static software.amazon.awssdk.codegen.poet.client.ClientClassUtils.addS3ArnableFieldCode;
2727
import static software.amazon.awssdk.codegen.poet.client.ClientClassUtils.applySignerOverrideMethod;
28+
import static software.amazon.awssdk.codegen.poet.client.SyncClientClass.addRequestModifierCode;
2829
import static software.amazon.awssdk.codegen.poet.client.SyncClientClass.getProtocolSpecs;
2930

3031
import com.squareup.javapoet.ClassName;
@@ -332,6 +333,7 @@ protected void addCloseMethod(TypeSpec.Builder type) {
332333
@Override
333334
protected MethodSpec.Builder operationBody(MethodSpec.Builder builder, OperationModel opModel) {
334335

336+
addRequestModifierCode(opModel, model).ifPresent(builder::addCode);
335337
builder.addModifiers(PUBLIC)
336338
.addAnnotation(Override.class);
337339
builder.addStatement("$T clientConfiguration = updateSdkClientConfiguration($L, this.clientConfiguration)",

Diff for: codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/SyncClientClass.java

+28
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@
2020
import static javax.lang.model.element.Modifier.PROTECTED;
2121
import static javax.lang.model.element.Modifier.PUBLIC;
2222
import static javax.lang.model.element.Modifier.STATIC;
23+
import static software.amazon.awssdk.codegen.poet.PoetUtils.classNameFromFqcn;
2324
import static software.amazon.awssdk.codegen.poet.client.ClientClassUtils.addS3ArnableFieldCode;
2425
import static software.amazon.awssdk.codegen.poet.client.ClientClassUtils.applySignerOverrideMethod;
2526

2627
import com.squareup.javapoet.ClassName;
28+
import com.squareup.javapoet.CodeBlock;
2729
import com.squareup.javapoet.FieldSpec;
2830
import com.squareup.javapoet.MethodSpec;
2931
import com.squareup.javapoet.ParameterizedTypeName;
@@ -36,6 +38,7 @@
3638
import java.util.List;
3739
import java.util.Map;
3840
import java.util.Objects;
41+
import java.util.Optional;
3942
import java.util.concurrent.CompletableFuture;
4043
import java.util.stream.Collectors;
4144
import java.util.stream.Stream;
@@ -50,6 +53,7 @@
5053
import software.amazon.awssdk.codegen.model.intermediate.OperationModel;
5154
import software.amazon.awssdk.codegen.model.intermediate.Protocol;
5255
import software.amazon.awssdk.codegen.model.service.ClientContextParam;
56+
import software.amazon.awssdk.codegen.model.service.PreClientExecutionRequestCustomizer;
5357
import software.amazon.awssdk.codegen.poet.PoetExtension;
5458
import software.amazon.awssdk.codegen.poet.PoetUtils;
5559
import software.amazon.awssdk.codegen.poet.auth.scheme.AuthSchemeSpecUtils;
@@ -249,6 +253,8 @@ private Stream<MethodSpec> operations(OperationModel opModel) {
249253
private MethodSpec traditionalMethod(OperationModel opModel) {
250254
MethodSpec.Builder method = SyncClientInterface.operationMethodSignature(model, opModel)
251255
.addAnnotation(Override.class);
256+
257+
addRequestModifierCode(opModel, model).ifPresent(method::addCode);
252258
if (!useSraAuth) {
253259
method.addCode(ClientClassUtils.callApplySignerOverrideMethod(opModel));
254260
}
@@ -341,6 +347,28 @@ private MethodSpec traditionalMethod(OperationModel opModel) {
341347
return method.build();
342348
}
343349

350+
public static Optional<CodeBlock> addRequestModifierCode(OperationModel opModel, IntermediateModel model) {
351+
352+
Map<String, PreClientExecutionRequestCustomizer> preClientExecutionRequestCustomizer =
353+
model.getCustomizationConfig().getPreClientExecutionRequestCustomizer();
354+
355+
if (!CollectionUtils.isNullOrEmpty(preClientExecutionRequestCustomizer)) {
356+
PreClientExecutionRequestCustomizer requestCustomizer =
357+
preClientExecutionRequestCustomizer.get(opModel.getOperationName());
358+
if (requestCustomizer != null) {
359+
CodeBlock.Builder builder = CodeBlock.builder();
360+
ClassName instanceType = classNameFromFqcn(requestCustomizer.getClassName());
361+
builder.addStatement("$L = $T.$N($L)",
362+
opModel.getInput().getVariableName(),
363+
instanceType,
364+
requestCustomizer.getMethodName(),
365+
opModel.getInput().getVariableName());
366+
return Optional.of(builder.build());
367+
}
368+
}
369+
return Optional.empty();
370+
}
371+
344372
@Override
345373
protected void addCloseMethod(TypeSpec.Builder type) {
346374
MethodSpec method = MethodSpec.methodBuilder("close")

Diff for: codegen/src/test/java/software/amazon/awssdk/codegen/internal/UtilsTest.java

+5
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,9 @@ public void testUnCapitalize() {
4343
capitalizedToUncapitalized.forEach((capitalized,unCapitalized) ->
4444
assertThat(Utils.unCapitalize(capitalized), is(equalTo(unCapitalized))));
4545
}
46+
47+
// Dummy No-op function which just returns the input as the return function.
48+
public static <T> T dummyRequestModifier(T input) {
49+
return input;
50+
}
4651
}

Diff for: codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/query/customization.config

+7-1
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,11 @@
2121
}
2222
}
2323
}
24-
]
24+
],
25+
"preClientExecutionRequestCustomizer": {
26+
"OperationWithCustomMember": {
27+
"methodName": "dummyRequestModifier",
28+
"className": "software.amazon.awssdk.codegen.internal.UtilsTest"
29+
}
30+
}
2531
}

Diff for: codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/query/service-2.json

+19
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,14 @@
8888
"encodings": ["gzip"]
8989
}
9090
},
91+
"OperationWithCustomMember": {
92+
"name": "OperationWithCustomMember",
93+
"http": {
94+
"method": "POST",
95+
"requestUri": "/"
96+
},
97+
"input":{"shape":"WithCustomMember"}
98+
},
9199
"APostOperation": {
92100
"name": "APostOperation",
93101
"http": {
@@ -202,6 +210,17 @@
202210
}
203211
}
204212
},
213+
"WithCustomMember": {
214+
"type": "structure",
215+
"members": {
216+
"StringMemberToBeUpdate" : {
217+
"shape": "String"
218+
},
219+
"StringMember": {
220+
"shape": "String"
221+
}
222+
}
223+
},
205224
"WithOperationContextParam": {
206225
"type": "structure",
207226
"members": {

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

+61
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import software.amazon.awssdk.awscore.exception.AwsServiceException;
1515
import software.amazon.awssdk.awscore.internal.AwsProtocolMetadata;
1616
import software.amazon.awssdk.awscore.internal.AwsServiceProtocol;
17+
import software.amazon.awssdk.codegen.internal.UtilsTest;
1718
import software.amazon.awssdk.core.CredentialType;
1819
import software.amazon.awssdk.core.RequestOverrideConfiguration;
1920
import software.amazon.awssdk.core.SdkPlugin;
@@ -51,6 +52,8 @@
5152
import software.amazon.awssdk.services.query.model.OperationWithChecksumRequiredResponse;
5253
import software.amazon.awssdk.services.query.model.OperationWithContextParamRequest;
5354
import software.amazon.awssdk.services.query.model.OperationWithContextParamResponse;
55+
import software.amazon.awssdk.services.query.model.OperationWithCustomMemberRequest;
56+
import software.amazon.awssdk.services.query.model.OperationWithCustomMemberResponse;
5457
import software.amazon.awssdk.services.query.model.OperationWithCustomizedOperationContextParamRequest;
5558
import software.amazon.awssdk.services.query.model.OperationWithCustomizedOperationContextParamResponse;
5659
import software.amazon.awssdk.services.query.model.OperationWithNoneAuthTypeRequest;
@@ -74,6 +77,7 @@
7477
import software.amazon.awssdk.services.query.transform.GetOperationWithChecksumRequestMarshaller;
7578
import software.amazon.awssdk.services.query.transform.OperationWithChecksumRequiredRequestMarshaller;
7679
import software.amazon.awssdk.services.query.transform.OperationWithContextParamRequestMarshaller;
80+
import software.amazon.awssdk.services.query.transform.OperationWithCustomMemberRequestMarshaller;
7781
import software.amazon.awssdk.services.query.transform.OperationWithCustomizedOperationContextParamRequestMarshaller;
7882
import software.amazon.awssdk.services.query.transform.OperationWithNoneAuthTypeRequestMarshaller;
7983
import software.amazon.awssdk.services.query.transform.OperationWithOperationContextParamRequestMarshaller;
@@ -470,6 +474,63 @@ public CompletableFuture<OperationWithContextParamResponse> operationWithContext
470474
}
471475
}
472476

477+
/**
478+
* Invokes the OperationWithCustomMember operation asynchronously.
479+
*
480+
* @param operationWithCustomMemberRequest
481+
* @return A Java Future containing the result of the OperationWithCustomMember operation returned by the service.<br/>
482+
* The CompletableFuture returned by this method can be completed exceptionally with the following
483+
* exceptions. The exception returned is wrapped with CompletionException, so you need to invoke
484+
* {@link Throwable#getCause} to retrieve the underlying exception.
485+
* <ul>
486+
* <li>SdkException Base class for all exceptions that can be thrown by the SDK (both service and client).
487+
* Can be used for catch all scenarios.</li>
488+
* <li>SdkClientException If any client side error occurs such as an IO related failure, failure to get
489+
* credentials, etc.</li>
490+
* <li>QueryException Base class for all service exceptions. Unknown exceptions will be thrown as an
491+
* instance of this type.</li>
492+
* </ul>
493+
* @sample QueryAsyncClient.OperationWithCustomMember
494+
* @see <a href="https://docs.aws.amazon.com/goto/WebAPI/query-service-2010-05-08/OperationWithCustomMember"
495+
* target="_top">AWS API Documentation</a>
496+
*/
497+
@Override
498+
public CompletableFuture<OperationWithCustomMemberResponse> operationWithCustomMember(
499+
OperationWithCustomMemberRequest operationWithCustomMemberRequest) {
500+
operationWithCustomMemberRequest = UtilsTest.dummyRequestModifier(operationWithCustomMemberRequest);
501+
SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(operationWithCustomMemberRequest,
502+
this.clientConfiguration);
503+
List<MetricPublisher> metricPublishers = resolveMetricPublishers(clientConfiguration, operationWithCustomMemberRequest
504+
.overrideConfiguration().orElse(null));
505+
MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector
506+
.create("ApiCall");
507+
try {
508+
apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Query Service");
509+
apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "OperationWithCustomMember");
510+
511+
HttpResponseHandler<OperationWithCustomMemberResponse> responseHandler = protocolFactory
512+
.createResponseHandler(OperationWithCustomMemberResponse::builder);
513+
514+
HttpResponseHandler<AwsServiceException> errorResponseHandler = protocolFactory.createErrorResponseHandler();
515+
516+
CompletableFuture<OperationWithCustomMemberResponse> executeFuture = clientHandler
517+
.execute(new ClientExecutionParams<OperationWithCustomMemberRequest, OperationWithCustomMemberResponse>()
518+
.withOperationName("OperationWithCustomMember").withProtocolMetadata(protocolMetadata)
519+
.withMarshaller(new OperationWithCustomMemberRequestMarshaller(protocolFactory))
520+
.withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler)
521+
.withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector)
522+
.withInput(operationWithCustomMemberRequest));
523+
CompletableFuture<OperationWithCustomMemberResponse> whenCompleteFuture = null;
524+
whenCompleteFuture = executeFuture.whenComplete((r, e) -> {
525+
metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect()));
526+
});
527+
return CompletableFutureUtils.forwardExceptionTo(whenCompleteFuture, executeFuture);
528+
} catch (Throwable t) {
529+
metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect()));
530+
return CompletableFutureUtils.failedFuture(t);
531+
}
532+
}
533+
473534
/**
474535
* Invokes the OperationWithCustomizedOperationContextParam operation asynchronously.
475536
*

0 commit comments

Comments
 (0)