Skip to content

Commit bcefbd3

Browse files
committed
Fix cloudwatch logs GetLogEvents auto-pagination bug
1 parent 32e301e commit bcefbd3

File tree

20 files changed

+719
-173
lines changed

20 files changed

+719
-173
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"category": "Amazon CloudWatch Logs",
3+
"type": "bugfix",
4+
"description": "Fix infinite pagination bug in CloudWatchLogsClient.getLogEventsPaginator API. See https://github.com/aws/aws-sdk-java-v2/issues/1045"
5+
}

codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/PaginatorsGeneratorTasks.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,23 @@
2525
import software.amazon.awssdk.codegen.emitters.GeneratorTask;
2626
import software.amazon.awssdk.codegen.emitters.GeneratorTaskParams;
2727
import software.amazon.awssdk.codegen.emitters.PoetGeneratorTask;
28+
import software.amazon.awssdk.codegen.model.config.customization.PaginationSubstitution;
2829
import software.amazon.awssdk.codegen.model.service.PaginatorDefinition;
2930
import software.amazon.awssdk.codegen.poet.ClassSpec;
3031
import software.amazon.awssdk.codegen.poet.paginators.AsyncResponseClassSpec;
3132
import software.amazon.awssdk.codegen.poet.paginators.SyncResponseClassSpec;
33+
import software.amazon.awssdk.codegen.poet.paginators.customizations.SameTokenAsyncResponseClassSpec;
34+
import software.amazon.awssdk.codegen.poet.paginators.customizations.SameTokenSyncResponseClassSpec;
3235

3336
public class PaginatorsGeneratorTasks extends BaseGeneratorTasks {
3437

3538
private final String paginatorsClassDir;
39+
private final Map<String, PaginationSubstitution> customization;
3640

3741
public PaginatorsGeneratorTasks(GeneratorTaskParams dependencies) {
3842
super(dependencies);
3943
this.paginatorsClassDir = dependencies.getPathProvider().getPaginatorsDirectory();
44+
this.customization = dependencies.getModel().getCustomizationConfig().getPaginationCustomization();
4045
}
4146

4247
@Override
@@ -62,12 +67,26 @@ private Stream<GeneratorTask> createSyncAndAsyncTasks(Map.Entry<String, Paginato
6267
private GeneratorTask createSyncTask(Map.Entry<String, PaginatorDefinition> entry) throws IOException {
6368
ClassSpec classSpec = new SyncResponseClassSpec(model, entry.getKey(), entry.getValue());
6469

70+
if (customization != null && customization.containsKey(entry.getKey())) {
71+
String sync = customization.get(entry.getKey()).getSync();
72+
if (sync != null && sync.equals("SameTokenSyncResponseClassSpec")) {
73+
classSpec = new SameTokenSyncResponseClassSpec(model, entry.getKey(), entry.getValue());
74+
}
75+
}
76+
6577
return new PoetGeneratorTask(paginatorsClassDir, model.getFileHeader(), classSpec);
6678
}
6779

6880
private GeneratorTask createAsyncTask(Map.Entry<String, PaginatorDefinition> entry) throws IOException {
6981
ClassSpec classSpec = new AsyncResponseClassSpec(model, entry.getKey(), entry.getValue());
7082

83+
if (customization != null && customization.containsKey(entry.getKey())) {
84+
String async = customization.get(entry.getKey()).getAsync();
85+
if (async != null && async.equals("SameTokenAsyncResponseClassSpec")) {
86+
classSpec = new SameTokenAsyncResponseClassSpec(model, entry.getKey(), entry.getValue());
87+
}
88+
}
89+
7190
return new PoetGeneratorTask(paginatorsClassDir, model.getFileHeader(), classSpec);
7291
}
7392

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,14 @@ public class CustomizationConfig {
147147
*/
148148
private String customProtocolFactoryFqcn;
149149

150+
/**
151+
* Map of paginated operations that use custom class generation.
152+
* Key - c2j operation name
153+
* Value - {@link PaginationSubstitution} object with details about the customizations for
154+
* sync and async response classes
155+
*/
156+
private Map<String, PaginationSubstitution> paginationCustomization;
157+
150158
private CustomizationConfig() {
151159
}
152160

@@ -378,4 +386,12 @@ public String getCustomProtocolFactoryFqcn() {
378386
public void setCustomProtocolFactoryFqcn(String customProtocolFactoryFqcn) {
379387
this.customProtocolFactoryFqcn = customProtocolFactoryFqcn;
380388
}
389+
390+
public Map<String, PaginationSubstitution> getPaginationCustomization() {
391+
return paginationCustomization;
392+
}
393+
394+
public void setPaginationCustomization(Map<String, PaginationSubstitution> paginationCustomization) {
395+
this.paginationCustomization = paginationCustomization;
396+
}
381397
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2010-2018 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.config.customization;
17+
18+
/**
19+
* Class that has the sync and async customization values for the auto-paginated operations.
20+
*/
21+
public class PaginationSubstitution {
22+
23+
/** Customization value to generate the response class for sync paginated operation */
24+
private String sync;
25+
26+
/** Customization value to generate the response class for Async paginated operation */
27+
private String async;
28+
29+
public String getSync() {
30+
return sync;
31+
}
32+
33+
public void setSync(String sync) {
34+
this.sync = sync;
35+
}
36+
37+
public String getAsync() {
38+
return async;
39+
}
40+
41+
public void setAsync(String async) {
42+
this.async = async;
43+
}
44+
}

codegen/src/main/java/software/amazon/awssdk/codegen/poet/paginators/AsyncResponseClassSpec.java

Lines changed: 24 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
import software.amazon.awssdk.codegen.poet.PoetUtils;
4141
import software.amazon.awssdk.core.async.SdkPublisher;
4242
import software.amazon.awssdk.core.pagination.async.AsyncPageFetcher;
43-
import software.amazon.awssdk.core.pagination.async.EmptySubscription;
4443
import software.amazon.awssdk.core.pagination.async.PaginatedItemsPublisher;
4544
import software.amazon.awssdk.core.pagination.async.ResponsesSubscription;
4645

@@ -49,9 +48,9 @@
4948
*/
5049
public class AsyncResponseClassSpec extends PaginatorsClassSpec {
5150

51+
protected static final String LAST_PAGE_FIELD = "isLastPage";
5252
private static final String SUBSCRIBER = "subscriber";
5353
private static final String SUBSCRIBE_METHOD = "subscribe";
54-
private static final String LAST_PAGE_FIELD = "isLastPage";
5554

5655
public AsyncResponseClassSpec(IntermediateModel model, String c2jOperationName, PaginatorDefinition paginatorDefinition) {
5756
super(model, c2jOperationName, paginatorDefinition);
@@ -63,19 +62,14 @@ public TypeSpec poetSpec() {
6362
.addModifiers(Modifier.PUBLIC)
6463
.addAnnotation(PoetUtils.generatedAnnotation())
6564
.addSuperinterface(getAsyncResponseInterface())
66-
.addFields(Stream.of(asyncClientInterfaceField(),
67-
requestClassField(),
68-
asyncPageFetcherField(),
69-
lastPageField())
70-
.collect(Collectors.toList()))
65+
.addFields(fields().collect(Collectors.toList()))
7166
.addMethod(publicConstructor())
7267
.addMethod(privateConstructor())
7368
.addMethod(subscribeMethod())
7469
.addMethods(getMethodSpecsForResultKeyList())
75-
.addMethod(resumeMethod())
7670
.addJavadoc(paginationDocs.getDocsForAsyncResponseClass(
7771
getAsyncClientInterfaceName()))
78-
.addType(nextPageFetcherClass());
72+
.addType(nextPageFetcherClass().build());
7973

8074
return specBuilder.build();
8175
}
@@ -95,23 +89,30 @@ private TypeName getAsyncResponseInterface() {
9589
/**
9690
* @return A Poet {@link ClassName} for the async client interface
9791
*/
98-
private ClassName getAsyncClientInterfaceName() {
92+
protected ClassName getAsyncClientInterfaceName() {
9993
return poetExtensions.getClientClass(model.getMetadata().getAsyncInterface());
10094
}
10195

102-
private FieldSpec asyncClientInterfaceField() {
96+
protected Stream<FieldSpec> fields() {
97+
return Stream.of(asyncClientInterfaceField(),
98+
requestClassField(),
99+
asyncPageFetcherField(),
100+
lastPageField());
101+
}
102+
103+
protected FieldSpec asyncClientInterfaceField() {
103104
return FieldSpec.builder(getAsyncClientInterfaceName(), CLIENT_MEMBER, Modifier.PRIVATE, Modifier.FINAL).build();
104105
}
105106

106107
private FieldSpec asyncPageFetcherField() {
107108
return FieldSpec.builder(AsyncPageFetcher.class, NEXT_PAGE_FETCHER_MEMBER, Modifier.PRIVATE, Modifier.FINAL).build();
108109
}
109110

110-
private FieldSpec lastPageField() {
111+
protected FieldSpec lastPageField() {
111112
return FieldSpec.builder(boolean.class, LAST_PAGE_FIELD, Modifier.PRIVATE).build();
112113
}
113114

114-
private MethodSpec publicConstructor() {
115+
protected MethodSpec publicConstructor() {
115116
return MethodSpec.constructorBuilder()
116117
.addModifiers(Modifier.PUBLIC)
117118
.addParameter(getAsyncClientInterfaceName(), CLIENT_MEMBER)
@@ -120,7 +121,7 @@ private MethodSpec publicConstructor() {
120121
.build();
121122
}
122123

123-
private MethodSpec privateConstructor() {
124+
protected MethodSpec privateConstructor() {
124125
return MethodSpec.constructorBuilder()
125126
.addModifiers(Modifier.PRIVATE)
126127
.addParameter(getAsyncClientInterfaceName(), CLIENT_MEMBER)
@@ -143,11 +144,16 @@ private MethodSpec subscribeMethod() {
143144
.addParameter(ParameterizedTypeName.get(ClassName.get(Subscriber.class),
144145
WildcardTypeName.supertypeOf(responseType())),
145146
SUBSCRIBER)
146-
.addStatement("$1L.onSubscribe($2T.builder().$1L($1L).$3L($3L).build())",
147-
SUBSCRIBER, ResponsesSubscription.class, NEXT_PAGE_FETCHER_MEMBER)
147+
.addStatement("$1L.onSubscribe($2T.builder().$1L($1L).$3L($4L).build())",
148+
SUBSCRIBER, ResponsesSubscription.class,
149+
NEXT_PAGE_FETCHER_MEMBER, nextPageFetcherArgument())
148150
.build();
149151
}
150152

153+
protected String nextPageFetcherArgument() {
154+
return NEXT_PAGE_FETCHER_MEMBER;
155+
}
156+
151157
/**
152158
* Returns iterable of {@link MethodSpec} to generate helper methods for all members
153159
* in {@link PaginatorDefinition#getResultKey()}.
@@ -220,7 +226,7 @@ PaginatedItemsPublisher.class, NEXT_PAGE_FETCHER_MEMBER, nextPageFetcherClassNam
220226
* Generates a inner class that implements {@link AsyncPageFetcher}. This is a helper class that can be used
221227
* to find if there are more pages in the response and to get the next page if exists.
222228
*/
223-
private TypeSpec nextPageFetcherClass() {
229+
protected TypeSpec.Builder nextPageFetcherClass() {
224230
return TypeSpec.classBuilder(nextPageFetcherClassName())
225231
.addModifiers(Modifier.PRIVATE)
226232
.addSuperinterface(ParameterizedTypeName.get(ClassName.get(AsyncPageFetcher.class), responseType()))
@@ -238,29 +244,6 @@ private TypeSpec nextPageFetcherClass() {
238244
.returns(ParameterizedTypeName.get(ClassName.get(CompletableFuture.class),
239245
responseType()))
240246
.addCode(nextPageMethodBody())
241-
.build())
242-
.build();
243-
}
244-
245-
private MethodSpec resumeMethod() {
246-
return resumeMethodBuilder().addCode(CodeBlock.builder()
247-
.addStatement("return $L", anonymousClassWithEmptySubscription())
248-
.build())
249-
.build();
250-
}
251-
252-
private TypeSpec anonymousClassWithEmptySubscription() {
253-
return TypeSpec.anonymousClassBuilder("$L, $L, true", CLIENT_MEMBER, REQUEST_MEMBER)
254-
.addSuperinterface(className())
255-
.addMethod(MethodSpec.methodBuilder(SUBSCRIBE_METHOD)
256-
.addAnnotation(Override.class)
257-
.addModifiers(Modifier.PUBLIC)
258-
.addParameter(ParameterizedTypeName.get(ClassName.get(Subscriber.class),
259-
WildcardTypeName.supertypeOf(responseType())),
260-
SUBSCRIBER)
261-
.addStatement("$L.onSubscribe(new $T($L))", SUBSCRIBER,
262-
TypeName.get(EmptySubscription.class), SUBSCRIBER)
263-
.build())
264-
.build();
247+
.build());
265248
}
266249
}

codegen/src/main/java/software/amazon/awssdk/codegen/poet/paginators/PaginatorsClassSpec.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,6 @@ protected MemberModel memberModelForResponseMember(String input) {
167167
}
168168

169169
protected CodeBlock hasNextPageMethodBody() {
170-
171170
if (paginatorDefinition.getMoreResults() != null) {
172171
return CodeBlock.builder()
173172
.add("return $N.$L.booleanValue()",
@@ -212,7 +211,7 @@ protected CodeBlock nextPageMethodBody() {
212211
* Sample generated code:
213212
* return client.listTables(firstRequest.toBuilder().exclusiveStartTableName(response.lastEvaluatedTableName()).build());
214213
*/
215-
private String codeToGetNextPageIfOldResponseIsNotNull() {
214+
protected String codeToGetNextPageIfOldResponseIsNotNull() {
216215
StringBuilder sb = new StringBuilder();
217216
sb.append(String.format("return %s.%s(%s)", CLIENT_MEMBER,
218217
operationModel.getMethodName(),
@@ -269,7 +268,7 @@ private String fluentSetterNameForSingleInputToken(String inputToken) {
269268
* Returns a list of fluent getter methods for members in {@link PaginatorDefinition#getOutputToken()} list.
270269
* The size of list returned by this method is equal to the size of {@link PaginatorDefinition#getOutputToken()} list.
271270
*/
272-
private List<String> fluentGetterMethodsForOutputToken() {
271+
protected List<String> fluentGetterMethodsForOutputToken() {
273272
return paginatorDefinition.getOutputToken().stream()
274273
.map(this::fluentGetterMethodForResponseMember)
275274
.collect(Collectors.toList());
@@ -285,7 +284,7 @@ private List<String> fluentGetterMethodsForOutputToken() {
285284
*
286285
* @param member A top level or nested member in response of {@link #c2jOperationName}.
287286
*/
288-
private String fluentGetterMethodForResponseMember(String member) {
287+
protected String fluentGetterMethodForResponseMember(String member) {
289288
String[] hierarchy = member.split("\\.");
290289

291290
if (hierarchy.length < 1) {

0 commit comments

Comments
 (0)