Skip to content

Commit c7b2909

Browse files
adamthom-amznmillems
authored andcommitted
Support event streams that are shared between two operations.
1 parent 5b1a1d1 commit c7b2909

File tree

14 files changed

+1011
-57
lines changed

14 files changed

+1011
-57
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"category": "AWS SDK for Java v2",
3+
"type": "bugfix",
4+
"description": "Support event streams that are shared between two operations."
5+
}

codegen/src/main/java/software/amazon/awssdk/codegen/poet/eventstream/EventStreamUtils.java

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515

1616
package software.amazon.awssdk.codegen.poet.eventstream;
1717

18+
import java.util.Collection;
1819
import java.util.Objects;
20+
import java.util.stream.Collectors;
1921
import java.util.stream.Stream;
2022
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
2123
import software.amazon.awssdk.codegen.model.intermediate.MemberModel;
@@ -116,16 +118,21 @@ public static Stream<MemberModel> getEventMembers(ShapeModel eventStreamShape) {
116118
}
117119

118120
/**
119-
* Returns the first operation that contains the given event stream shape. The event stream can be in operation
121+
* Returns the all operations that contain the given event stream shape. The event stream can be in operation
120122
* request or response shape.
121123
*/
122-
public static OperationModel findOperationWithEventStream(IntermediateModel model, ShapeModel eventStreamShape) {
123-
return model.getOperations().values()
124+
public static Collection<OperationModel> findOperationsWithEventStream(IntermediateModel model, ShapeModel eventStreamShape) {
125+
Collection<OperationModel> operations = model.getOperations().values()
124126
.stream()
125127
.filter(op -> operationContainsEventStream(op, eventStreamShape))
126-
.findFirst()
127-
.orElseThrow(() -> new IllegalStateException(String.format(
128-
"%s is an event shape but has no corresponding operation in the model", eventStreamShape.getC2jName())));
128+
.collect(Collectors.toList());
129+
130+
if (operations.isEmpty()) {
131+
throw new IllegalStateException(String.format(
132+
"%s is an event shape but has no corresponding operation in the model", eventStreamShape.getC2jName()));
133+
}
134+
135+
return operations;
129136
}
130137

131138
/**

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

Lines changed: 84 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import com.squareup.javapoet.WildcardTypeName;
2929
import java.io.Serializable;
3030
import java.util.ArrayList;
31+
import java.util.Collection;
3132
import java.util.Collections;
3233
import java.util.List;
3334
import java.util.Optional;
@@ -84,46 +85,40 @@ public AwsServiceModel(IntermediateModel intermediateModel, ShapeModel shapeMode
8485
@Override
8586
public TypeSpec poetSpec() {
8687
if (shapeModel.isEventStream()) {
87-
OperationModel opModel = EventStreamUtils.findOperationWithEventStream(intermediateModel,
88-
shapeModel);
89-
String apiName = poetExtensions.getApiName(opModel);
90-
ClassName modelClass = poetExtensions.getModelClassFromShape(shapeModel);
88+
Collection<OperationModel> opModels = EventStreamUtils.findOperationsWithEventStream(intermediateModel,
89+
shapeModel);
9190

92-
if (EventStreamUtils.doesShapeContainsEventStream(opModel.getOutputShape(), shapeModel)) {
93-
ClassName responseHandlerClass = poetExtensions.eventStreamResponseHandlerType(opModel);
94-
return PoetUtils.createInterfaceBuilder(modelClass)
95-
.addAnnotation(SdkPublicApi.class)
96-
.addSuperinterface(ClassName.get(SdkPojo.class))
97-
.addJavadoc("Base interface for all event types of the $L API.", apiName)
98-
.addField(FieldSpec.builder(modelClass, "UNKNOWN")
99-
.addModifiers(PUBLIC, Modifier.STATIC, Modifier.FINAL)
100-
.initializer(CodeBlock.builder()
101-
.add("new $T() {\n"
102-
+ " @Override\n"
103-
+ " public $T<$T<?>> sdkFields() {\n"
104-
+ " return $T.emptyList();\n"
105-
+ " }\n"
106-
+ " @Override\n"
107-
+ " public void accept($T.Visitor visitor) {"
108-
+ " \nvisitor.visitDefault(this);\n"
109-
+ " }\n"
110-
+ " };\n",
111-
modelClass, List.class, SdkField.class,
112-
Collections.class, responseHandlerClass
113-
)
114-
.build())
115-
.addJavadoc("Special type of {@link $T} for unknown types of events that this "
116-
+ "version of the SDK does not know about", modelClass)
117-
.build())
118-
.addMethod(acceptMethodSpec(modelClass, responseHandlerClass)
119-
.addModifiers(Modifier.ABSTRACT)
120-
.build())
121-
.build();
91+
Collection<OperationModel> outputOperations = findOutputEventStreamOperations(opModels, shapeModel);
12292

123-
} else if (EventStreamUtils.doesShapeContainsEventStream(opModel.getInputShape(), shapeModel)) {
93+
ClassName modelClass = poetExtensions.getModelClassFromShape(shapeModel);
94+
95+
if (!outputOperations.isEmpty()) {
96+
CodeBlock unknownInitializer = buildUnknownEventStreamInitializer(outputOperations,
97+
modelClass);
98+
99+
TypeSpec.Builder builder =
100+
PoetUtils.createInterfaceBuilder(modelClass)
101+
.addAnnotation(SdkPublicApi.class)
102+
.addSuperinterface(ClassName.get(SdkPojo.class))
103+
.addJavadoc("Base interface for all event types in $L.", shapeModel.getShapeName())
104+
.addField(FieldSpec.builder(modelClass, "UNKNOWN")
105+
.addModifiers(PUBLIC, Modifier.STATIC, Modifier.FINAL)
106+
.initializer(unknownInitializer)
107+
.addJavadoc("Special type of {@link $T} for unknown types of events that this "
108+
+ "version of the SDK does not know about", modelClass)
109+
.build());
110+
111+
for (OperationModel opModel : outputOperations) {
112+
ClassName responseHandlerClass = poetExtensions.eventStreamResponseHandlerType(opModel);
113+
builder.addMethod(acceptMethodSpec(modelClass, responseHandlerClass)
114+
.addModifiers(Modifier.ABSTRACT)
115+
.build());
116+
}
117+
return builder.build();
118+
} else if (hasInputStreamOperations(opModels, shapeModel)) {
124119
return PoetUtils.createInterfaceBuilder(modelClass)
125120
.addAnnotation(SdkPublicApi.class)
126-
.addJavadoc("Base interface for all event types of the $L API.", apiName)
121+
.addJavadoc("Base interface for all event types in $L.", shapeModel.getShapeName())
127122
.build();
128123
}
129124

@@ -160,21 +155,25 @@ public TypeSpec poetSpec() {
160155
if (this.shapeModel.isEvent()) {
161156
ShapeModel eventStream = EventStreamUtils.getBaseEventStreamShape(intermediateModel, shapeModel);
162157
ClassName eventStreamClassName = poetExtensions.getModelClassFromShape(eventStream);
163-
OperationModel opModel = EventStreamUtils.findOperationWithEventStream(intermediateModel,
158+
Collection<OperationModel> opModels = EventStreamUtils.findOperationsWithEventStream(intermediateModel,
164159
eventStream);
165160

166-
if (EventStreamUtils.doesShapeContainsEventStream(opModel.getOutputShape(), eventStream)) {
161+
Collection<OperationModel> outputOperations = findOutputEventStreamOperations(opModels, eventStream);
162+
163+
if (!outputOperations.isEmpty()) {
167164
ClassName modelClass = poetExtensions.getModelClass(shapeModel.getShapeName());
168-
ClassName responseHandlerClass = poetExtensions.eventStreamResponseHandlerType(opModel);
169165
specBuilder.addSuperinterface(eventStreamClassName);
170-
specBuilder.addMethod(acceptMethodSpec(modelClass, responseHandlerClass)
171-
.addAnnotation(Override.class)
172-
.addCode(CodeBlock.builder()
173-
.addStatement("visitor.visit(this)")
174-
.build())
175-
.build());
176-
177-
} else if (EventStreamUtils.doesShapeContainsEventStream(opModel.getInputShape(), eventStream)) {
166+
for (OperationModel opModel : outputOperations) {
167+
ClassName responseHandlerClass = poetExtensions.eventStreamResponseHandlerType(opModel);
168+
specBuilder.addMethod(acceptMethodSpec(modelClass, responseHandlerClass)
169+
.addAnnotation(Override.class)
170+
.addCode(CodeBlock.builder()
171+
.addStatement("visitor.visit(this)")
172+
.build())
173+
.build());
174+
}
175+
176+
} else if (hasInputStreamOperations(opModels, eventStream)) {
178177
specBuilder.addSuperinterface(eventStreamClassName);
179178
} else {
180179
throw new IllegalArgumentException(shapeModel.getC2jName() + " event shape is not a member in any "
@@ -190,6 +189,44 @@ public TypeSpec poetSpec() {
190189
}
191190
}
192191

192+
private boolean hasInputStreamOperations(Collection<OperationModel> opModels, ShapeModel eventStream) {
193+
return opModels.stream()
194+
.anyMatch(op -> EventStreamUtils.doesShapeContainsEventStream(op.getInputShape(), eventStream));
195+
}
196+
197+
private List<OperationModel> findOutputEventStreamOperations(Collection<OperationModel> opModels,
198+
ShapeModel eventStream) {
199+
return opModels
200+
.stream()
201+
.filter(opModel -> EventStreamUtils.doesShapeContainsEventStream(opModel.getOutputShape(), eventStream))
202+
.collect(Collectors.toList());
203+
}
204+
205+
private CodeBlock buildUnknownEventStreamInitializer(Collection<OperationModel> outputOperations,
206+
ClassName eventStreamModelClass) {
207+
CodeBlock.Builder builder = CodeBlock.builder()
208+
.add("new $T() {\n"
209+
+ " @Override\n"
210+
+ " public $T<$T<?>> sdkFields() {\n"
211+
+ " return $T.emptyList();\n"
212+
+ " }\n",
213+
eventStreamModelClass, List.class, SdkField.class,
214+
Collections.class
215+
);
216+
217+
for (OperationModel opModel : outputOperations) {
218+
ClassName responseHandlerClass = poetExtensions.eventStreamResponseHandlerType(opModel);
219+
builder.add(" @Override\n"
220+
+ " public void accept($T.Visitor visitor) {"
221+
+ " \nvisitor.visitDefault(this);\n"
222+
+ " }\n", responseHandlerClass);
223+
}
224+
225+
builder.add(" }\n");
226+
227+
return builder.build();
228+
}
229+
193230
private MethodSpec sdkFieldsMethod() {
194231
ParameterizedTypeName sdkFieldType = ParameterizedTypeName.get(ClassName.get(SdkField.class),
195232
WildcardTypeName.subtypeOf(ClassName.get(Object.class)));
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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.poet.model;
17+
18+
import static java.util.stream.Collectors.toList;
19+
import static org.hamcrest.MatcherAssert.assertThat;
20+
import static software.amazon.awssdk.codegen.poet.PoetMatchers.generatesTo;
21+
import static software.amazon.awssdk.utils.FunctionalUtils.invokeSafely;
22+
23+
import java.io.File;
24+
import java.io.IOException;
25+
import java.util.Collection;
26+
import java.util.Locale;
27+
import org.junit.Test;
28+
import org.junit.runner.RunWith;
29+
import org.junit.runners.Parameterized;
30+
import software.amazon.awssdk.codegen.C2jModels;
31+
import software.amazon.awssdk.codegen.IntermediateModelBuilder;
32+
import software.amazon.awssdk.codegen.model.config.customization.CustomizationConfig;
33+
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
34+
import software.amazon.awssdk.codegen.model.intermediate.ShapeModel;
35+
import software.amazon.awssdk.codegen.model.service.ServiceModel;
36+
import software.amazon.awssdk.codegen.utils.ModelLoaderUtils;
37+
38+
@RunWith(Parameterized.class)
39+
public class SharedStreamAwsModelSpecTest {
40+
private static IntermediateModel intermediateModel;
41+
42+
private final ShapeModel shapeModel;
43+
44+
@Parameterized.Parameters(name = "{0}")
45+
public static Collection<Object[]> data() {
46+
invokeSafely(SharedStreamAwsModelSpecTest::setUp);
47+
return intermediateModel.getShapes().values().stream().map(shape -> new Object[] { shape }).collect(toList());
48+
}
49+
50+
public SharedStreamAwsModelSpecTest(ShapeModel shapeModel) {
51+
this.shapeModel = shapeModel;
52+
}
53+
54+
@Test
55+
public void basicGeneration() throws Exception {
56+
assertThat(new AwsServiceModel(intermediateModel, shapeModel), generatesTo(referenceFileForShape()));
57+
}
58+
59+
private String referenceFileForShape() {
60+
return "sharedstream/" + shapeModel.getShapeName().toLowerCase(Locale.ENGLISH) + ".java";
61+
}
62+
63+
private static void setUp() throws IOException {
64+
File serviceModelFile = new File(SharedStreamAwsModelSpecTest.class.getResource("sharedstream/service-2.json").getFile());
65+
ServiceModel serviceModel = ModelLoaderUtils.loadModel(ServiceModel.class, serviceModelFile);
66+
67+
intermediateModel = new IntermediateModelBuilder(
68+
C2jModels.builder()
69+
.serviceModel(serviceModel)
70+
.customizationConfig(CustomizationConfig.create())
71+
.build())
72+
.build();
73+
}
74+
}

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/eventstream.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import software.amazon.awssdk.core.SdkPojo;
99

1010
/**
11-
* Base interface for all event types of the EventStreamOperation API.
11+
* Base interface for all event types in EventStream.
1212
*/
1313
@Generated("software.amazon.awssdk:codegen")
1414
@SdkPublicApi
@@ -26,7 +26,7 @@ public List<SdkField<?>> sdkFields() {
2626
public void accept(EventStreamOperationResponseHandler.Visitor visitor) {
2727
visitor.visitDefault(this);
2828
}
29-
};;
29+
};
3030

3131
/**
3232
* Calls the appropriate visit method depending on the subtype of {@link EventStream}.

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/inputeventstream.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import software.amazon.awssdk.annotations.SdkPublicApi;
55

66
/**
7-
* Base interface for all event types of the EventStreamOperation API.
7+
* Base interface for all event types in InputEventStream.
88
*/
99
@Generated("software.amazon.awssdk:codegen")
1010
@SdkPublicApi

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/inputeventstreamtwo.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import software.amazon.awssdk.annotations.SdkPublicApi;
55

66
/**
7-
* Base interface for all event types of the EventStreamOperationWithOnlyInput API.
7+
* Base interface for all event types in InputEventStreamTwo.
88
*/
99
@Generated("software.amazon.awssdk:codegen")
1010
@SdkPublicApi
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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.services.sharedeventstream.model;
17+
18+
import java.util.Collections;
19+
import java.util.List;
20+
import software.amazon.awssdk.annotations.Generated;
21+
import software.amazon.awssdk.annotations.SdkPublicApi;
22+
import software.amazon.awssdk.core.SdkField;
23+
import software.amazon.awssdk.core.SdkPojo;
24+
25+
/**
26+
* Base interface for all event types in EventStream.
27+
*/
28+
@Generated("software.amazon.awssdk:codegen")
29+
@SdkPublicApi
30+
public interface EventStream extends SdkPojo {
31+
/**
32+
* Special type of {@link EventStream} for unknown types of events that this version of the SDK does not know about
33+
*/
34+
EventStream UNKNOWN = new EventStream() {
35+
@Override
36+
public List<SdkField<?>> sdkFields() {
37+
return Collections.emptyList();
38+
}
39+
40+
@Override
41+
public void accept(StreamBirthsResponseHandler.Visitor visitor) {
42+
visitor.visitDefault(this);
43+
}
44+
45+
@Override
46+
public void accept(StreamDeathsResponseHandler.Visitor visitor) {
47+
visitor.visitDefault(this);
48+
}
49+
};
50+
51+
/**
52+
* Calls the appropriate visit method depending on the subtype of {@link EventStream}.
53+
*
54+
* @param visitor Visitor to invoke.
55+
*/
56+
void accept(StreamBirthsResponseHandler.Visitor visitor);
57+
58+
/**
59+
* Calls the appropriate visit method depending on the subtype of {@link EventStream}.
60+
*
61+
* @param visitor Visitor to invoke.
62+
*/
63+
void accept(StreamDeathsResponseHandler.Visitor visitor);
64+
}

0 commit comments

Comments
 (0)