Skip to content

Commit fa38518

Browse files
adamthom-amznmillems
authored andcommitted
Allow event structures to be used as operation outputs outside of streaming contexts.
1 parent ad3af5a commit fa38518

File tree

9 files changed

+417
-45
lines changed

9 files changed

+417
-45
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": "Allow event structures to be used as operation outputs outside of streaming contexts."
5+
}

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,7 @@ private void linkMembersToShapes(IntermediateModel model) {
168168
for (Map.Entry<String, ShapeModel> entry : model.getShapes().entrySet()) {
169169
if (entry.getValue().getMembers() != null) {
170170
for (MemberModel member : entry.getValue().getMembers()) {
171-
member.setShape(
172-
Utils.findShapeModelByC2jNameIfExists(model, member.getC2jShape()));
171+
member.setShape(Utils.findMemberShapeModelByC2jNameIfExists(model, member.getC2jShape()));
173172
}
174173
}
175174
}

codegen/src/main/java/software/amazon/awssdk/codegen/internal/Utils.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import software.amazon.awssdk.codegen.model.intermediate.Metadata;
3030
import software.amazon.awssdk.codegen.model.intermediate.ShapeMarshaller;
3131
import software.amazon.awssdk.codegen.model.intermediate.ShapeModel;
32+
import software.amazon.awssdk.codegen.model.intermediate.ShapeType;
3233
import software.amazon.awssdk.codegen.model.service.Input;
3334
import software.amazon.awssdk.codegen.model.service.Operation;
3435
import software.amazon.awssdk.codegen.model.service.ServiceMetadata;
@@ -289,6 +290,28 @@ public static ShapeModel findShapeModelByC2jNameIfExists(IntermediateModel inter
289290
return null;
290291
}
291292

293+
/**
294+
* Search for a shape model by its C2J name, excluding request and response shapes, which are not candidates to be members
295+
* of another shape.
296+
*
297+
* @return ShapeModel or null if the shape doesn't exist (if it's primitive or container type for example)
298+
*/
299+
public static ShapeModel findMemberShapeModelByC2jNameIfExists(IntermediateModel intermediateModel, String shapeC2jName) {
300+
ShapeModel candidate = null;
301+
for (ShapeModel shape : intermediateModel.getShapes().values()) {
302+
if (shape.getShapeType() != ShapeType.Request
303+
&& shape.getShapeType() != ShapeType.Response
304+
&& shape.getC2jName().equals(shapeC2jName)) {
305+
if (candidate != null) {
306+
throw new IllegalStateException("Conflicting candidates for member model with C2J name " + shapeC2jName + ": "
307+
+ candidate + " and " + shape);
308+
}
309+
candidate = shape;
310+
}
311+
}
312+
return candidate;
313+
}
314+
292315
public static List<ShapeModel> findShapesByC2jName(IntermediateModel intermediateModel, String shapeC2jName) {
293316
return intermediateModel.getShapes().values().stream().filter(s -> s.getC2jName().equals(shapeC2jName)).collect(toList());
294317
}

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

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.util.Collection;
1919
import java.util.Objects;
20+
import java.util.Optional;
2021
import java.util.stream.Collectors;
2122
import java.util.stream.Stream;
2223
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
@@ -57,10 +58,11 @@ public static ShapeModel getEventStreamInResponse(ShapeModel responseShape) {
5758
}
5859

5960
/**
60-
* Get event stream shape from a request/response shape model. Otherwise return empty optional.
61+
* Get event stream shape from a request/response shape model. Otherwise, throw
6162
*
6263
* @param shapeModel request or response shape of an operation
63-
* @return Optional containing the Eventstream shape
64+
* @return the EventStream shape
65+
* @throws IllegalStateException if there is no associated event stream shape
6466
*/
6567
private static ShapeModel eventStreamFrom(ShapeModel shapeModel) {
6668
if (shapeModel == null || shapeModel.getMembers() == null) {
@@ -82,17 +84,15 @@ private static ShapeModel eventStreamFrom(ShapeModel shapeModel) {
8284
*
8385
* @param model Intermediate model
8486
* @param eventShape shape with "event: true" trait
85-
* @return the event stream shape (eventstream: true) that contains the given event.
87+
* @return the event stream shape (eventstream: true) that contains the given event, or an empty optional if the C2J shape
88+
* is marked as an event but the intermediate model representation is not used by an event stream
8689
*/
87-
public static ShapeModel getBaseEventStreamShape(IntermediateModel model, ShapeModel eventShape) {
90+
public static Optional<ShapeModel> getBaseEventStreamShape(IntermediateModel model, ShapeModel eventShape) {
8891
return model.getShapes().values()
8992
.stream()
9093
.filter(ShapeModel::isEventStream)
9194
.filter(s -> s.getMembers().stream().anyMatch(m -> m.getShape().equals(eventShape)))
92-
.findFirst()
93-
.orElseThrow(() -> new IllegalStateException(
94-
String.format("Event shape %s not referenced in model by any eventstream shape",
95-
eventShape.getC2jName())));
95+
.findFirst();
9696
}
9797

9898
/**
@@ -152,14 +152,11 @@ public static boolean doesShapeContainsEventStream(ShapeModel parentShape, Shape
152152
* Returns true if the given event shape is a sub-member of any operation request.
153153
*/
154154
public static boolean isRequestEvent(IntermediateModel model, ShapeModel eventShape) {
155-
try {
156-
ShapeModel eventStreamShape = getBaseEventStreamShape(model, eventShape);
157-
return model.getOperations().values()
158-
.stream()
159-
.anyMatch(o -> doesShapeContainsEventStream(o.getInputShape(), eventStreamShape));
160-
} catch (IllegalStateException e) {
161-
return false;
162-
}
155+
return getBaseEventStreamShape(model, eventShape)
156+
.map(stream -> model.getOperations().values()
157+
.stream()
158+
.anyMatch(o -> doesShapeContainsEventStream(o.getInputShape(), stream)))
159+
.orElse(false);
163160
}
164161

165162
private static boolean operationContainsEventStream(OperationModel opModel, ShapeModel eventStreamShape) {

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

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -153,32 +153,9 @@ public TypeSpec poetSpec() {
153153
}
154154

155155
if (this.shapeModel.isEvent()) {
156-
ShapeModel eventStream = EventStreamUtils.getBaseEventStreamShape(intermediateModel, shapeModel);
157-
ClassName eventStreamClassName = poetExtensions.getModelClassFromShape(eventStream);
158-
Collection<OperationModel> opModels = EventStreamUtils.findOperationsWithEventStream(intermediateModel,
159-
eventStream);
160-
161-
Collection<OperationModel> outputOperations = findOutputEventStreamOperations(opModels, eventStream);
162-
163-
if (!outputOperations.isEmpty()) {
164-
ClassName modelClass = poetExtensions.getModelClass(shapeModel.getShapeName());
165-
specBuilder.addSuperinterface(eventStreamClassName);
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)) {
177-
specBuilder.addSuperinterface(eventStreamClassName);
178-
} else {
179-
throw new IllegalArgumentException(shapeModel.getC2jName() + " event shape is not a member in any "
180-
+ "request or response event shape");
181-
}
156+
EventStreamUtils.getBaseEventStreamShape(intermediateModel, shapeModel).ifPresent(
157+
eventStream -> addEventSupport(specBuilder, eventStream)
158+
);
182159
}
183160

184161
if (this.shapeModel.getDocumentation() != null) {
@@ -189,6 +166,33 @@ public TypeSpec poetSpec() {
189166
}
190167
}
191168

169+
private void addEventSupport(TypeSpec.Builder specBuilder, ShapeModel eventStream) {
170+
ClassName eventStreamClassName = poetExtensions.getModelClassFromShape(eventStream);
171+
Collection<OperationModel> opModels = EventStreamUtils.findOperationsWithEventStream(intermediateModel,
172+
eventStream);
173+
174+
Collection<OperationModel> outputOperations = findOutputEventStreamOperations(opModels, eventStream);
175+
176+
if (!outputOperations.isEmpty()) {
177+
ClassName modelClass = poetExtensions.getModelClass(shapeModel.getShapeName());
178+
specBuilder.addSuperinterface(eventStreamClassName);
179+
for (OperationModel opModel : outputOperations) {
180+
ClassName responseHandlerClass = poetExtensions.eventStreamResponseHandlerType(opModel);
181+
specBuilder.addMethod(acceptMethodSpec(modelClass, responseHandlerClass)
182+
.addAnnotation(Override.class)
183+
.addCode(CodeBlock.builder()
184+
.addStatement("visitor.visit(this)")
185+
.build())
186+
.build());
187+
}
188+
} else if (hasInputStreamOperations(opModels, eventStream)) {
189+
specBuilder.addSuperinterface(eventStreamClassName);
190+
} else {
191+
throw new IllegalArgumentException(shapeModel.getC2jName() + " event shape is not a member in any "
192+
+ "request or response event shape");
193+
}
194+
}
195+
192196
private boolean hasInputStreamOperations(Collection<OperationModel> opModels, ShapeModel eventStream) {
193197
return opModels.stream()
194198
.anyMatch(op -> EventStreamUtils.doesShapeContainsEventStream(op.getInputShape(), eventStream));

codegen/src/main/java/software/amazon/awssdk/codegen/poet/transform/protocols/EventStreamJsonMarshallerSpec.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,9 @@ protected FieldSpec operationInfoField() {
8484
}
8585

8686
private String getMemberNameFromEventStream() {
87-
ShapeModel eventStream = EventStreamUtils.getBaseEventStreamShape(intermediateModel, shapeModel);
87+
ShapeModel eventStream = EventStreamUtils.getBaseEventStreamShape(intermediateModel, shapeModel)
88+
.orElseThrow(() -> new IllegalStateException("Could not find associated event stream spec for "
89+
+ shapeModel.getC2jName()));
8890
return eventStream.getMembers().stream()
8991
.filter(memberModel -> memberModel.getShape().equals(shapeModel))
9092
.findAny()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package software.amazon.awssdk.services.sharedeventstream.model;
2+
3+
import java.util.Arrays;
4+
import java.util.Collections;
5+
import java.util.List;
6+
import java.util.Optional;
7+
import java.util.function.Consumer;
8+
import software.amazon.awssdk.annotations.Generated;
9+
import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration;
10+
import software.amazon.awssdk.core.SdkField;
11+
import software.amazon.awssdk.core.SdkPojo;
12+
import software.amazon.awssdk.utils.ToString;
13+
import software.amazon.awssdk.utils.builder.CopyableBuilder;
14+
import software.amazon.awssdk.utils.builder.ToCopyableBuilder;
15+
16+
@Generated("software.amazon.awssdk:codegen")
17+
public final class GetRandomPersonRequest extends SharedEventStreamRequest implements
18+
ToCopyableBuilder<GetRandomPersonRequest.Builder, GetRandomPersonRequest> {
19+
private static final List<SdkField<?>> SDK_FIELDS = Collections.unmodifiableList(Arrays.asList());
20+
21+
private GetRandomPersonRequest(BuilderImpl builder) {
22+
super(builder);
23+
}
24+
25+
@Override
26+
public Builder toBuilder() {
27+
return new BuilderImpl(this);
28+
}
29+
30+
public static Builder builder() {
31+
return new BuilderImpl();
32+
}
33+
34+
public static Class<? extends Builder> serializableBuilderClass() {
35+
return BuilderImpl.class;
36+
}
37+
38+
@Override
39+
public int hashCode() {
40+
int hashCode = 1;
41+
hashCode = 31 * hashCode + super.hashCode();
42+
return hashCode;
43+
}
44+
45+
@Override
46+
public boolean equals(Object obj) {
47+
return super.equals(obj) && equalsBySdkFields(obj);
48+
}
49+
50+
@Override
51+
public boolean equalsBySdkFields(Object obj) {
52+
if (this == obj) {
53+
return true;
54+
}
55+
if (obj == null) {
56+
return false;
57+
}
58+
if (!(obj instanceof GetRandomPersonRequest)) {
59+
return false;
60+
}
61+
return true;
62+
}
63+
64+
/**
65+
* Returns a string representation of this object. This is useful for testing and debugging. Sensitive data will be
66+
* redacted from this string using a placeholder value.
67+
*/
68+
@Override
69+
public String toString() {
70+
return ToString.builder("GetRandomPersonRequest").build();
71+
}
72+
73+
public <T> Optional<T> getValueForField(String fieldName, Class<T> clazz) {
74+
return Optional.empty();
75+
}
76+
77+
@Override
78+
public List<SdkField<?>> sdkFields() {
79+
return SDK_FIELDS;
80+
}
81+
82+
public interface Builder extends SharedEventStreamRequest.Builder, SdkPojo, CopyableBuilder<Builder, GetRandomPersonRequest> {
83+
@Override
84+
Builder overrideConfiguration(AwsRequestOverrideConfiguration overrideConfiguration);
85+
86+
@Override
87+
Builder overrideConfiguration(Consumer<AwsRequestOverrideConfiguration.Builder> builderConsumer);
88+
}
89+
90+
static final class BuilderImpl extends SharedEventStreamRequest.BuilderImpl implements Builder {
91+
private BuilderImpl() {
92+
}
93+
94+
private BuilderImpl(GetRandomPersonRequest model) {
95+
super(model);
96+
}
97+
98+
@Override
99+
public Builder overrideConfiguration(AwsRequestOverrideConfiguration overrideConfiguration) {
100+
super.overrideConfiguration(overrideConfiguration);
101+
return this;
102+
}
103+
104+
@Override
105+
public Builder overrideConfiguration(Consumer<AwsRequestOverrideConfiguration.Builder> builderConsumer) {
106+
super.overrideConfiguration(builderConsumer);
107+
return this;
108+
}
109+
110+
@Override
111+
public GetRandomPersonRequest build() {
112+
return new GetRandomPersonRequest(this);
113+
}
114+
115+
@Override
116+
public List<SdkField<?>> sdkFields() {
117+
return SDK_FIELDS;
118+
}
119+
}
120+
}

0 commit comments

Comments
 (0)