Skip to content

Commit 891089d

Browse files
cenedhrynmillems
authored andcommitted
Fixed an issue where DynamoDB's QueryResponse.Builder (or anything else with a List<Map<String, AttributeValue>>)
could not be serialized using a bean-based serializer.
1 parent 743b744 commit 891089d

File tree

14 files changed

+1391
-1043
lines changed

14 files changed

+1391
-1043
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"category": "Amazon DynamoDB",
3+
"contributor": "",
4+
"type": "bugfix",
5+
"description": "Fixed an issue where structure builders containing List<Map<String, Shape>> could not be marshalled using bean-based serializers."
6+
}

codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/MemberModel.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,12 @@ public boolean isCollectionWithBuilderMember() {
620620
(isMap() && getMapModel().getValueModel() != null && getMapModel().getValueModel().hasBuilder());
621621
}
622622

623+
@JsonIgnore
624+
public boolean isCollectionWithNestedBuilderMember() {
625+
return isList() && getListModel().getListMemberModel() != null && getListModel().isMap() &&
626+
getListModel().getListMemberModel().getMapModel().getValueModel().hasBuilder();
627+
}
628+
623629
@JsonIgnore
624630
public boolean isSdkBytesType() {
625631
return SdkBytes.class.getName().equals(variable.getVariableType());

codegen/src/main/java/software/amazon/awssdk/codegen/poet/model/AbstractMemberSetters.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.stream.Collectors;
2929
import javax.lang.model.element.Modifier;
3030
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
31+
import software.amazon.awssdk.codegen.model.intermediate.MapModel;
3132
import software.amazon.awssdk.codegen.model.intermediate.MemberModel;
3233
import software.amazon.awssdk.codegen.model.intermediate.ShapeModel;
3334
import software.amazon.awssdk.codegen.poet.PoetExtensions;
@@ -124,7 +125,7 @@ protected CodeBlock copySetterBuilderBody() {
124125
"this.$1N = $1N != null ? $1N.build() : null",
125126
serviceModelCopiers.copyMethodName());
126127
}
127-
if (memberModel.isCollectionWithBuilderMember()) {
128+
if (memberModel.isCollectionWithBuilderMember() || memberModel.isCollectionWithNestedBuilderMember()) {
128129
return copySetterBody("this.$1N = $2T.$3N($1N)", null, serviceModelCopiers.builderCopyMethodName());
129130
}
130131
return copySetterBody();
@@ -175,6 +176,19 @@ protected ParameterSpec memberAsBeanStyleParameter() {
175176
}
176177

177178
if (memberModel.isList()) {
179+
if (memberModel.isCollectionWithNestedBuilderMember()) {
180+
MapModel nestedMapModel = memberModel.getListModel().getListMemberModel().getMapModel();
181+
TypeName nestedMapKeyType = typeProvider.getTypeNameForSimpleType(nestedMapModel.getKeyModel()
182+
.getVariable()
183+
.getVariableType());
184+
ClassName nestedMapValueType = poetExtensions.getModelClass(nestedMapModel.getValueModel().getC2jShape());
185+
TypeName nestedMapReturnType = ParameterizedTypeName.get(ClassName.get(Map.class),
186+
nestedMapKeyType,
187+
nestedMapValueType.nestedClass("BuilderImpl"));
188+
TypeName listType = ParameterizedTypeName.get(ClassName.get(Collection.class), nestedMapReturnType);
189+
return ParameterSpec.builder(listType, fieldName()).build();
190+
}
191+
178192
MemberModel listMember = memberModel.getListModel().getListMemberModel();
179193

180194
if (hasBuilder(listMember)) {
@@ -249,4 +263,5 @@ private CodeBlock copySetterBody(String copyAssignment, String regularAssignment
249263
private boolean hasBuilder(MemberModel model) {
250264
return model != null && model.hasBuilder();
251265
}
266+
252267
}

codegen/src/main/java/software/amazon/awssdk/codegen/poet/model/BeanGetterHelper.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.Map;
2727
import java.util.stream.Collectors;
2828
import javax.lang.model.element.Modifier;
29+
import software.amazon.awssdk.codegen.model.intermediate.MapModel;
2930
import software.amazon.awssdk.codegen.model.intermediate.MemberModel;
3031
import software.amazon.awssdk.codegen.poet.PoetExtensions;
3132
import software.amazon.awssdk.codegen.poet.PoetUtils;
@@ -48,6 +49,9 @@ public MethodSpec beanStyleGetter(MemberModel memberModel) {
4849
if (memberModel.isCollectionWithBuilderMember()) {
4950
return memberModel.isList() ? listOfBuildersGetter(memberModel) : mapOfBuildersGetter(memberModel);
5051
}
52+
if (memberModel.isCollectionWithNestedBuilderMember()) {
53+
return listOfMapOfBuilderGetter(memberModel);
54+
}
5155
if (memberModel.isSdkBytesType()) {
5256
return byteBufferGetter(memberModel);
5357
}
@@ -96,6 +100,28 @@ private MethodSpec builderGetter(MemberModel memberModel) {
96100
memberModel.getVariable().getVariableName()));
97101
}
98102

103+
private MethodSpec listOfMapOfBuilderGetter(MemberModel memberModel) {
104+
MapModel nestedMapModel = memberModel.getListModel().getListMemberModel().getMapModel();
105+
TypeName nestedMapKeyType = typeProvider.getTypeNameForSimpleType(nestedMapModel.getKeyModel()
106+
.getVariable().getVariableType());
107+
ClassName nestedMapValueType = poetExtensions.getModelClass(nestedMapModel.getValueModel().getC2jShape());
108+
TypeName nestedMapReturnType = ParameterizedTypeName.get(ClassName.get(Map.class),
109+
nestedMapKeyType,
110+
nestedMapValueType.nestedClass("Builder"));
111+
112+
TypeName returnType = ParameterizedTypeName.get(ClassName.get(Collection.class), nestedMapReturnType);
113+
114+
CodeBlock mapReturnStatement =
115+
CodeBlock.of("Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue().toBuilder())");
116+
117+
CodeBlock returnStatement = CodeBlock.of("return $1N != null ? $1N.stream().map(m -> m.entrySet().stream().collect("
118+
+ mapReturnStatement + ")).collect($2T.toList()) : null;",
119+
memberModel.getVariable().getVariableName(),
120+
Collectors.class);
121+
122+
return basicGetter(memberModel, returnType, returnStatement);
123+
}
124+
99125
private MethodSpec mapOfBuildersGetter(MemberModel memberModel) {
100126
TypeName keyType = typeProvider.getTypeNameForSimpleType(memberModel.getMapModel().getKeyModel()
101127
.getVariable().getVariableType());

codegen/src/main/java/software/amazon/awssdk/codegen/poet/model/ListSetters.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,12 @@ public List<MethodSpec> fluent(TypeName returnType) {
119119

120120
@Override
121121
public List<MethodSpec> beanStyle() {
122-
MethodSpec.Builder builder = beanStyleSetterBuilder()
123-
.addCode(memberModel().isCollectionWithBuilderMember() ? copySetterBuilderBody() : beanCopySetterBody());
122+
MethodSpec.Builder builder = beanStyleSetterBuilder();
123+
if (memberModel().isCollectionWithBuilderMember()) {
124+
builder.addCode(copySetterBuilderBody());
125+
} else {
126+
builder.addCode(beanCopySetterBody());
127+
}
124128

125129
return Collections.singletonList(builder.build());
126130

codegen/src/main/java/software/amazon/awssdk/codegen/poet/model/MemberCopierSpec.java

Lines changed: 84 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,51 @@ private CodeBlock stringToEnumCopyMethodBody() {
190190
}
191191

192192
private MethodSpec builderCopyMethod() {
193+
return builderCopyMethodProto()
194+
.addParameter(builderCopyMethodParameters(), memberParamName())
195+
.addCode(builderCopyMethodBody())
196+
.build();
197+
}
198+
199+
private MethodSpec.Builder builderCopyMethodProto() {
200+
return MethodSpec.methodBuilder(serviceModelCopiers.builderCopyMethodName())
201+
.addModifiers(Modifier.STATIC)
202+
.returns(typeProvider.fieldType(memberModel));
203+
}
204+
205+
private TypeName builderCopyMethodParameters() {
206+
if (memberModel.isCollectionWithNestedBuilderMember()) {
207+
ParameterizedTypeName nestedParameters = builderCopyMethodParametersForMap(memberModel.getListModel()
208+
.getListMemberModel()
209+
.getMapModel());
210+
return ParameterizedTypeName.get(ClassName.get(Collection.class), WildcardTypeName.subtypeOf(nestedParameters));
211+
}
212+
if (memberModel.isList()) {
213+
return builderCopyMethodParametersForList();
214+
}
215+
if (memberModel.isMap()) {
216+
return builderCopyMethodParametersForMap(memberModel.getMapModel());
217+
}
218+
throw new UnsupportedOperationException();
219+
}
220+
221+
private ParameterizedTypeName builderCopyMethodParametersForList() {
222+
ClassName listParameter = poetExtensions.getModelClass(memberModel.getListModel().getListMemberModel().getC2jShape());
223+
ClassName builderForParameter = listParameter.nestedClass("Builder");
224+
return ParameterizedTypeName.get(ClassName.get(Collection.class), WildcardTypeName.subtypeOf(builderForParameter));
225+
}
226+
227+
private ParameterizedTypeName builderCopyMethodParametersForMap(MapModel mapModel) {
228+
TypeName keyType = typeProvider.getTypeNameForSimpleType(mapModel.getKeyModel().getVariable().getVariableType());
229+
ClassName valueParameter = poetExtensions.getModelClass(mapModel.getValueModel().getC2jShape());
230+
ClassName builderForParameter = valueParameter.nestedClass("Builder");
231+
return ParameterizedTypeName.get(ClassName.get(Map.class), keyType, WildcardTypeName.subtypeOf(builderForParameter));
232+
}
233+
234+
private CodeBlock builderCopyMethodBody() {
235+
if (memberModel.isCollectionWithNestedBuilderMember()) {
236+
return builderCopyMethodForListWithMap();
237+
}
193238
if (memberModel.isList()) {
194239
return builderCopyMethodForList();
195240
}
@@ -199,59 +244,50 @@ private MethodSpec builderCopyMethod() {
199244
throw new UnsupportedOperationException();
200245
}
201246

202-
private MethodSpec builderCopyMethodForMap() {
203-
TypeName keyType = typeProvider.getTypeNameForSimpleType(memberModel.getMapModel().getKeyModel()
204-
.getVariable().getVariableType());
205-
ClassName valueParameter = poetExtensions.getModelClass(memberModel.getMapModel().getValueModel().getC2jShape());
206-
ClassName builderForParameter = valueParameter.nestedClass("Builder");
207-
TypeName parameterType =
208-
ParameterizedTypeName.get(ClassName.get(Map.class), keyType, WildcardTypeName.subtypeOf(builderForParameter));
209-
210-
CodeBlock code =
211-
CodeBlock.builder()
212-
.beginControlFlow("if ($1N == null || $1N instanceof $2T)",
213-
memberParamName(), DefaultSdkAutoConstructMap.class)
214-
.addStatement("return $T.getInstance()", DefaultSdkAutoConstructMap.class)
215-
.endControlFlow()
216-
.addStatement("return $N($N.entrySet().stream().collect(toMap($T::getKey, e -> e.getValue().build())))",
217-
serviceModelCopiers.copyMethodName(),
218-
memberParamName(),
219-
Map.Entry.class)
220-
.build();
247+
private CodeBlock builderCopyMethodForListWithMap() {
248+
MemberModel listMemberModel = memberModel.getListModel().getListMemberModel();
249+
Optional<ClassName> mapCopierClass = serviceModelCopiers.copierClassFor(listMemberModel);
221250

222-
return MethodSpec.methodBuilder(serviceModelCopiers.builderCopyMethodName())
223-
.addModifiers(Modifier.STATIC)
224-
.addParameter(parameterType, memberParamName())
225-
.returns(typeProvider.fieldType(memberModel))
226-
.addCode(code)
227-
.build();
251+
CodeBlock.Builder builderMethodBody = builderWithDefaultStatement(DefaultSdkAutoConstructList.class);
252+
builderMethodBody.add("return $N($N.stream().", serviceModelCopiers.copyMethodName(), memberParamName());
253+
254+
if (mapCopierClass.isPresent()) {
255+
builderMethodBody.add("map($T::$N)", mapCopierClass.get(), serviceModelCopiers.builderCopyMethodName());
256+
} else {
257+
builderMethodBody.add("map(m -> m.entrySet().stream().collect(toMap(Map.Entry::getKey, e -> e.getValue().build())))");
258+
}
259+
260+
builderMethodBody.addStatement(".collect(toList()))");
261+
return builderMethodBody.build();
228262
}
229263

230-
private MethodSpec builderCopyMethodForList() {
231-
ClassName listParameter = poetExtensions.getModelClass(memberModel.getListModel().getListMemberModel().getC2jShape());
232-
ClassName builderForParameter = listParameter.nestedClass("Builder");
264+
private CodeBlock builderCopyMethodForMap() {
265+
return builderWithDefaultStatement(DefaultSdkAutoConstructMap.class)
266+
.addStatement("return $N($N.entrySet().stream().collect(toMap($T::getKey, e -> e.getValue().build())))",
267+
serviceModelCopiers.copyMethodName(),
268+
memberParamName(),
269+
Map.Entry.class)
270+
.build();
271+
}
233272

234-
TypeName parameterType =
235-
ParameterizedTypeName.get(ClassName.get(Collection.class), WildcardTypeName.subtypeOf(builderForParameter));
236-
237-
CodeBlock code = CodeBlock.builder()
238-
.beginControlFlow("if ($1N == null || $1N instanceof $2T)",
239-
memberParamName(), DefaultSdkAutoConstructList.class)
240-
.addStatement("return $T.getInstance()", DefaultSdkAutoConstructList.class)
241-
.endControlFlow()
242-
.addStatement("return $N($N.stream().map($T::$N).collect(toList()))",
243-
serviceModelCopiers.copyMethodName(),
244-
memberParamName(),
245-
builderForParameter,
246-
"build")
247-
.build();
273+
private CodeBlock builderCopyMethodForList() {
274+
String listMemberClass = memberModel.getListModel().getListMemberModel().getC2jShape();
248275

249-
return MethodSpec.methodBuilder(serviceModelCopiers.builderCopyMethodName())
250-
.addModifiers(Modifier.STATIC)
251-
.addParameter(parameterType, memberParamName())
252-
.returns(typeProvider.fieldType(memberModel))
253-
.addCode(code)
254-
.build();
276+
return builderWithDefaultStatement(DefaultSdkAutoConstructList.class)
277+
.addStatement("return $N($N.stream().map($T::$N).collect(toList()))",
278+
serviceModelCopiers.copyMethodName(),
279+
memberParamName(),
280+
poetExtensions.getModelClass(listMemberClass).nestedClass("Builder"),
281+
"build")
282+
.build();
283+
}
284+
285+
private CodeBlock.Builder builderWithDefaultStatement(Class<?> autoConstructClass) {
286+
return CodeBlock.builder()
287+
.beginControlFlow("if ($1N == null || $1N instanceof $2T)",
288+
memberParamName(), autoConstructClass)
289+
.addStatement("return $T.getInstance()", autoConstructClass)
290+
.endControlFlow();
255291
}
256292

257293
private CodeBlock copyMethodBody() {

codegen/src/main/java/software/amazon/awssdk/codegen/poet/model/ServiceModelCopiers.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,13 @@ public Collection<ClassSpec> copierSpecs() {
5050
}
5151

5252
public boolean requiresBuilderCopier(MemberModel memberModel) {
53+
if (memberModel.isCollectionWithNestedBuilderMember()) {
54+
return true;
55+
}
5356
if (memberModel.isList()) {
5457
MemberModel type = memberModel.getListModel().getListMemberModel();
5558
return type != null && type.hasBuilder();
5659
}
57-
5860
if (memberModel.isMap()) {
5961
MemberModel valueType = memberModel.getMapModel().getValueModel();
6062
return valueType != null && valueType.hasBuilder();

codegen/src/test/java/software/amazon/awssdk/codegen/poet/model/AwsModelSpecTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ public AwsModelSpecTest(ShapeModel shapeModel) {
5353

5454
@Test
5555
public void basicGeneration() {
56-
assertThat(new AwsServiceModel(intermediateModel, shapeModel), generatesTo(referenceFileForShape()));
56+
AwsServiceModel actual = new AwsServiceModel(intermediateModel, shapeModel);
57+
assertThat(actual, generatesTo(referenceFileForShape()));
5758
}
5859

5960
private String referenceFileForShape() {

0 commit comments

Comments
 (0)