Skip to content

Commit 5f8d718

Browse files
committed
Add support for request event streams
1 parent 0d390d9 commit 5f8d718

File tree

84 files changed

+2650
-417
lines changed

Some content is hidden

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

84 files changed

+2650
-417
lines changed

build-tools/src/main/resources/software/amazon/awssdk/checkstyle-suppressions.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,4 @@
4242
<!-- Assumes getter/setter match JSON property -->
4343
<suppress checks="AbbreviationAsWordInName"
4444
files=".*[\\/]software[\\/]amazon[\\/]awssdk[\\/]services[\\/]s3[\\/]event[\\/]S3EventNotification.java$"/>
45-
</suppressions>
45+
</suppressions>

build-tools/src/main/resources/software/amazon/awssdk/spotbugs-suppressions.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,4 @@
104104
<Bug pattern="BC_UNCONFIRMED_CAST,SIC_INNER_SHOULD_BE_STATIC_ANON,DLS_DEAD_LOCAL_STORE,DM_CONVERT_CASE,NM_CLASS_NOT_EXCEPTION,NP_NULL_ON_SOME_PATH"/>
105105
</Match>
106106

107-
</FindBugsFilter>
107+
</FindBugsFilter>

codegen/src/main/java/software/amazon/awssdk/codegen/AddShapes.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@ private MemberModel generateMemberModel(String c2jMemberName, Member c2jMemberDe
173173
.withBeanStyleGetterMethodName(namingStrategy.getBeanStyleGetterMethodName(c2jMemberName))
174174
.withBeanStyleSetterMethodName(namingStrategy.getBeanStyleSetterMethodName(c2jMemberName));
175175
memberModel.setIdempotencyToken(c2jMemberDefinition.isIdempotencyToken());
176+
memberModel.setEventPayload(c2jMemberDefinition.isEventPayload());
177+
memberModel.setEventHeader(c2jMemberDefinition.isEventHeader());
176178

177179
// Pass the xmlNameSpace from the member reference
178180
if (c2jMemberDefinition.getXmlNamespace() != null) {

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import software.amazon.awssdk.codegen.poet.ClassSpec;
2626
import software.amazon.awssdk.codegen.poet.eventstream.EventStreamResponseHandlerBuilderImplSpec;
2727
import software.amazon.awssdk.codegen.poet.eventstream.EventStreamResponseHandlerSpec;
28-
import software.amazon.awssdk.codegen.poet.eventstream.EventStreamUtils;
2928
import software.amazon.awssdk.codegen.poet.eventstream.EventStreamVisitorBuilderImplSpec;
3029

3130
/**
@@ -54,10 +53,9 @@ protected List<GeneratorTask> createTasks() throws Exception {
5453
}
5554

5655
private Stream<ClassSpec> eventStreamClassSpecs(OperationModel opModel) {
57-
EventStreamUtils eventStreamUtils = EventStreamUtils.create(params.getPoetExtensions(), opModel);
5856
return Stream.of(
59-
new EventStreamResponseHandlerSpec(params, eventStreamUtils),
60-
new EventStreamResponseHandlerBuilderImplSpec(params, eventStreamUtils),
61-
new EventStreamVisitorBuilderImplSpec(params, eventStreamUtils));
57+
new EventStreamResponseHandlerSpec(params, opModel),
58+
new EventStreamResponseHandlerBuilderImplSpec(params, opModel),
59+
new EventStreamVisitorBuilderImplSpec(params, opModel));
6260
}
6361
}

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

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ public class MemberModel extends DocumentationModel {
7474

7575
private String beanStyleSetterName;
7676

77+
private boolean eventPayload;
78+
79+
private boolean eventHeader;
80+
7781
public String getName() {
7882
return name;
7983
}
@@ -274,6 +278,22 @@ public void setDeprecated(boolean deprecated) {
274278
this.deprecated = deprecated;
275279
}
276280

281+
public boolean isEventPayload() {
282+
return eventPayload;
283+
}
284+
285+
public void setEventPayload(boolean eventPayload) {
286+
this.eventPayload = eventPayload;
287+
}
288+
289+
public boolean isEventHeader() {
290+
return eventHeader;
291+
}
292+
293+
public void setEventHeader(boolean eventHeader) {
294+
this.eventHeader = eventHeader;
295+
}
296+
277297
public ListModel getListModel() {
278298
return listModel;
279299
}
@@ -448,7 +468,8 @@ public void setIdempotencyToken(boolean idempotencyToken) {
448468
}
449469

450470
public boolean getIsBinary() {
451-
return http.getIsStreaming() || (http.getIsPayload() && isSdkBytesType());
471+
return http.getIsStreaming() ||
472+
(isSdkBytesType() && (http.getIsPayload() || isEventPayload()));
452473
}
453474

454475
/**

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

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -193,11 +193,21 @@ public void setPaginated(boolean paginated) {
193193
* @return True if the operation has an event stream member in the output shape. False otherwise.
194194
*/
195195
public boolean hasEventStreamOutput() {
196-
return outputShape != null
197-
&& outputShape.getMembers() != null
198-
&& outputShape.getMembers().stream()
199-
.filter(m -> m.getShape() != null)
200-
.anyMatch(m -> m.getShape().isEventStream());
196+
return containsEventStream(outputShape);
201197
}
202198

199+
/**
200+
* @return True if the operation has an event stream member in the input shape. False otherwise.
201+
*/
202+
public boolean hasEventStreamInput() {
203+
return containsEventStream(inputShape);
204+
}
205+
206+
private boolean containsEventStream(ShapeModel shapeModel) {
207+
return shapeModel != null
208+
&& shapeModel.getMembers() != null
209+
&& shapeModel.getMembers().stream()
210+
.filter(m -> m.getShape() != null)
211+
.anyMatch(m -> m.getShape().isEventStream());
212+
}
203213
}

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

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,11 +175,53 @@ public List<MemberModel> getUnboundMembers() {
175175
return unboundMembers;
176176
}
177177

178+
/**
179+
* @return The list of members whose are not marked with either eventheader or eventpayload trait.
180+
*/
181+
@JsonIgnore
182+
public List<MemberModel> getUnboundEventMembers() {
183+
if (members == null) {
184+
return new ArrayList<>();
185+
}
186+
187+
return members.stream()
188+
.filter(m -> !m.isEventHeader())
189+
.filter(m -> !m.isEventPayload())
190+
.collect(Collectors.toList());
191+
}
192+
178193
/**
179194
* @return True if the shape has an explicit payload member or implicit payload member(s).
180195
*/
181196
public boolean hasPayloadMembers() {
182-
return hasPayloadMember || getUnboundMembers().size() > 0;
197+
return hasPayloadMember ||
198+
getExplicitEventPayloadMember() != null ||
199+
getUnboundMembers().size() > 0 ||
200+
getUnboundEventMembers().size() > 0;
201+
}
202+
203+
/**
204+
* Explicit event payload member will have "eventpayload" trait set to true.
205+
* There can be at most only one member that can be declared as explicit payload.
206+
*
207+
* @return the member that has the 'eventpayload' trait set to true. If none found, return null.
208+
*/
209+
public MemberModel getExplicitEventPayloadMember() {
210+
if (members == null) {
211+
return null;
212+
}
213+
214+
return members.stream()
215+
.filter(m -> m.isEventPayload())
216+
.findFirst()
217+
.orElse(null);
218+
}
219+
220+
/**
221+
* If all members in shape have eventheader trait, then there is no payload
222+
*/
223+
public boolean hasNoEventPayload() {
224+
return members == null || members.stream().allMatch(m -> m.isEventHeader());
183225
}
184226

185227
public boolean isHasStreamingMember() {

codegen/src/main/java/software/amazon/awssdk/codegen/model/service/Member.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
package software.amazon.awssdk.codegen.model.service;
1717

18+
import com.fasterxml.jackson.annotation.JsonProperty;
19+
1820
public class Member {
1921

2022
private String shape;
@@ -39,6 +41,12 @@ public class Member {
3941

4042
private boolean deprecated;
4143

44+
@JsonProperty(value = "eventpayload")
45+
private boolean eventPayload;
46+
47+
@JsonProperty(value = "eventheader")
48+
private boolean eventHeader;
49+
4250
public String getShape() {
4351
return shape;
4452
}
@@ -126,4 +134,20 @@ public boolean isDeprecated() {
126134
public void setDeprecated(boolean deprecated) {
127135
this.deprecated = deprecated;
128136
}
137+
138+
public boolean isEventPayload() {
139+
return eventPayload;
140+
}
141+
142+
public void setEventPayload(boolean eventPayload) {
143+
this.eventPayload = eventPayload;
144+
}
145+
146+
public boolean isEventHeader() {
147+
return eventHeader;
148+
}
149+
150+
public void setEventHeader(boolean eventHeader) {
151+
this.eventHeader = eventHeader;
152+
}
129153
}

codegen/src/main/java/software/amazon/awssdk/codegen/poet/PoetExtensions.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616
package software.amazon.awssdk.codegen.poet;
1717

1818
import com.squareup.javapoet.ClassName;
19+
import software.amazon.awssdk.codegen.internal.Utils;
1920
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
21+
import software.amazon.awssdk.codegen.model.intermediate.OperationModel;
22+
import software.amazon.awssdk.codegen.model.intermediate.ShapeModel;
2023

2124
/**
2225
* Extension and convenience methods to Poet that use the intermediate model.
@@ -90,4 +93,55 @@ public ClassName getResponseMetadataClass() {
9093
return ClassName.get(model.getMetadata().getFullModelPackageName(),
9194
model.getSdkResponseBaseClassName() + "Metadata");
9295
}
96+
97+
/**
98+
* @return The correctly cased name of the API.
99+
*/
100+
public String getApiName(OperationModel operation) {
101+
return Utils.capitalize(operation.getOperationName());
102+
}
103+
104+
/**
105+
* @return The {@link ClassName} for the response pojo.
106+
*/
107+
public ClassName responsePojoType(OperationModel operation) {
108+
return getModelClass(operation.getOutputShape().getShapeName());
109+
}
110+
111+
// TODO Should we move the event stream specific methods to a new class
112+
/**
113+
* @return {@link ClassName} for generated event stream response handler interface.
114+
*/
115+
public ClassName eventStreamResponseHandlerType(OperationModel operation) {
116+
return getModelClass(getApiName(operation) + "ResponseHandler");
117+
}
118+
119+
/**
120+
* @return {@link ClassName} for the builder interface for the response handler interface
121+
*/
122+
public ClassName eventStreamResponseHandlerBuilderType(OperationModel operation) {
123+
return eventStreamResponseHandlerType(operation).nestedClass("Builder");
124+
}
125+
126+
/**
127+
* @return {@link ClassName} for the event stream visitor interface.
128+
*/
129+
public ClassName eventStreamResponseHandlerVisitorType(OperationModel operation) {
130+
return eventStreamResponseHandlerType(operation).nestedClass("Visitor");
131+
}
132+
133+
/**
134+
* @return {@link ClassName} for the builder interface for the event stream visitor interface.
135+
*/
136+
public ClassName eventStreamResponseHandlerVisitorBuilderType(OperationModel operation) {
137+
return eventStreamResponseHandlerVisitorType(operation).nestedClass("Builder");
138+
}
139+
140+
/**
141+
* @param shapeModel shape model for the class in model package
142+
* @return {@link ClassName} for the shape represented by the given {@link ShapeModel}.
143+
*/
144+
public ClassName getModelClassFromShape(ShapeModel shapeModel) {
145+
return getModelClass(shapeModel.getShapeName());
146+
}
93147
}

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

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,23 +23,35 @@
2323
import static software.amazon.awssdk.codegen.poet.client.SyncClientClass.getProtocolSpecs;
2424

2525
import com.squareup.javapoet.ClassName;
26+
import com.squareup.javapoet.CodeBlock;
2627
import com.squareup.javapoet.FieldSpec;
2728
import com.squareup.javapoet.MethodSpec;
29+
import com.squareup.javapoet.ParameterizedTypeName;
30+
import com.squareup.javapoet.TypeName;
2831
import com.squareup.javapoet.TypeSpec;
32+
import java.nio.ByteBuffer;
33+
import java.util.List;
2934
import java.util.concurrent.Executor;
35+
import java.util.stream.Collectors;
3036
import javax.lang.model.element.Modifier;
37+
import org.reactivestreams.Publisher;
3138
import org.slf4j.Logger;
3239
import org.slf4j.LoggerFactory;
3340
import software.amazon.awssdk.annotations.SdkInternalApi;
3441
import software.amazon.awssdk.awscore.client.handler.AwsAsyncClientHandler;
42+
import software.amazon.awssdk.awscore.eventstream.EventStreamTaggedUnionJsonMarshaller;
43+
import software.amazon.awssdk.awscore.internal.client.handler.AwsClientHandlerUtils;
3544
import software.amazon.awssdk.awscore.protocol.json.AwsJsonProtocolFactory;
3645
import software.amazon.awssdk.codegen.emitters.GeneratorTaskParams;
3746
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
3847
import software.amazon.awssdk.codegen.model.intermediate.OperationModel;
48+
import software.amazon.awssdk.codegen.model.intermediate.ShapeModel;
3949
import software.amazon.awssdk.codegen.poet.PoetExtensions;
4050
import software.amazon.awssdk.codegen.poet.PoetUtils;
4151
import software.amazon.awssdk.codegen.poet.StaticImport;
4252
import software.amazon.awssdk.codegen.poet.client.specs.ProtocolSpec;
53+
import software.amazon.awssdk.codegen.poet.eventstream.EventStreamUtils;
54+
import software.amazon.awssdk.core.async.SdkPublisher;
4355
import software.amazon.awssdk.core.client.config.SdkAdvancedAsyncClientOption;
4456
import software.amazon.awssdk.core.client.handler.AsyncClientHandler;
4557
import software.amazon.awssdk.core.internal.client.config.SdkClientConfiguration;
@@ -158,7 +170,8 @@ protected MethodSpec.Builder operationBody(MethodSpec.Builder builder, Operation
158170
.addCode(getCustomResponseHandler(opModel, returnType)
159171
.orElseGet(() -> protocolSpec.responseHandler(model, opModel)))
160172
.addCode(protocolSpec.errorResponseHandler(opModel))
161-
.addCode(protocolSpec.asyncExecutionHandler(opModel))
173+
.addCode(eventToByteBufferPublisher(opModel))
174+
.addCode(protocolSpec.asyncExecutionHandler(model, opModel))
162175
.endControlFlow()
163176
.beginControlFlow("catch ($T t)", Throwable.class);
164177

@@ -190,4 +203,46 @@ public ClassName className() {
190203
public Iterable<StaticImport> staticImports() {
191204
return singletonList(StaticImport.staticMethodImport(FunctionalUtils.class, "runAndLogError"));
192205
}
206+
207+
private CodeBlock eventToByteBufferPublisher(OperationModel opModel) {
208+
if (!opModel.hasEventStreamInput()) {
209+
return CodeBlock.builder().build();
210+
}
211+
212+
ShapeModel eventStreamShape = EventStreamUtils.getEventStreamInRequest(opModel.getInputShape());
213+
CodeBlock code = CodeBlock.builder()
214+
.add(createEventStreamTaggedUnionJsonMarshaller(eventStreamShape))
215+
.addStatement("$1T eventPublisher = $2T.adapt($3L)",
216+
ParameterizedTypeName.get(
217+
ClassName.get(SdkPublisher.class),
218+
eventStreamType(eventStreamShape)),
219+
SdkPublisher.class,
220+
EVENT_PUBLISHER_PARAM_NAME)
221+
.add("$T adapted = eventPublisher.map(event -> eventMarshaller.marshall(event))",
222+
ParameterizedTypeName.get(Publisher.class, ByteBuffer.class))
223+
.add(".map($T::encodeEventStreamRequestToByteBuffer);", AwsClientHandlerUtils.class)
224+
.build();
225+
226+
return code;
227+
}
228+
229+
private CodeBlock createEventStreamTaggedUnionJsonMarshaller(ShapeModel eventStreamShape) {
230+
CodeBlock.Builder builder = CodeBlock.builder().add("$1T eventMarshaller = $1T.builder()",
231+
EventStreamTaggedUnionJsonMarshaller.class);
232+
233+
List<String> eventNames = EventStreamUtils.getEvents(eventStreamShape)
234+
.map(shape -> shape.getShapeName())
235+
.collect(Collectors.toList());
236+
237+
eventNames.forEach(event -> builder.add(".putMarshaller($T.class, new $T(protocolFactory))",
238+
poetExtensions.getModelClass(event),
239+
poetExtensions.getTransformClass(event + "Marshaller")));
240+
241+
builder.add(".build();");
242+
return builder.build();
243+
}
244+
245+
private TypeName eventStreamType(ShapeModel shapeModel) {
246+
return poetExtensions.getModelClass(shapeModel.getShapeName());
247+
}
193248
}

0 commit comments

Comments
 (0)