Skip to content

Allow event structures to be used as operation outputs outside of streaming contexts. #1837

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/next-release/bugfix-AWSSDKforJavav2-eb912cc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"category": "AWS SDK for Java v2",
"type": "bugfix",
"description": "Allow event structures to be used as operation outputs outside of streaming contexts."
}
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,7 @@ private void linkMembersToShapes(IntermediateModel model) {
for (Map.Entry<String, ShapeModel> entry : model.getShapes().entrySet()) {
if (entry.getValue().getMembers() != null) {
for (MemberModel member : entry.getValue().getMembers()) {
member.setShape(
Utils.findShapeModelByC2jNameIfExists(model, member.getC2jShape()));
member.setShape(Utils.findMemberShapeModelByC2jNameIfExists(model, member.getC2jShape()));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import software.amazon.awssdk.codegen.model.intermediate.Metadata;
import software.amazon.awssdk.codegen.model.intermediate.ShapeMarshaller;
import software.amazon.awssdk.codegen.model.intermediate.ShapeModel;
import software.amazon.awssdk.codegen.model.intermediate.ShapeType;
import software.amazon.awssdk.codegen.model.service.Input;
import software.amazon.awssdk.codegen.model.service.Operation;
import software.amazon.awssdk.codegen.model.service.ServiceMetadata;
Expand Down Expand Up @@ -289,6 +290,28 @@ public static ShapeModel findShapeModelByC2jNameIfExists(IntermediateModel inter
return null;
}

/**
* Search for a shape model by its C2J name, excluding request and response shapes, which are not candidates to be members
* of another shape.
*
* @return ShapeModel or null if the shape doesn't exist (if it's primitive or container type for example)
*/
public static ShapeModel findMemberShapeModelByC2jNameIfExists(IntermediateModel intermediateModel, String shapeC2jName) {
ShapeModel candidate = null;
for (ShapeModel shape : intermediateModel.getShapes().values()) {
if (shape.getShapeType() != ShapeType.Request
&& shape.getShapeType() != ShapeType.Response
&& shape.getC2jName().equals(shapeC2jName)) {
if (candidate != null) {
throw new IllegalStateException("Conflicting candidates for member model with C2J name " + shapeC2jName + ": "
+ candidate + " and " + shape);
}
candidate = shape;
}
}
return candidate;
}

public static List<ShapeModel> findShapesByC2jName(IntermediateModel intermediateModel, String shapeC2jName) {
return intermediateModel.getShapes().values().stream().filter(s -> s.getC2jName().equals(shapeC2jName)).collect(toList());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
Expand Down Expand Up @@ -57,10 +58,11 @@ public static ShapeModel getEventStreamInResponse(ShapeModel responseShape) {
}

/**
* Get event stream shape from a request/response shape model. Otherwise return empty optional.
* Get event stream shape from a request/response shape model. Otherwise, throw
*
* @param shapeModel request or response shape of an operation
* @return Optional containing the Eventstream shape
* @return the EventStream shape
* @throws IllegalStateException if there is no associated event stream shape
*/
private static ShapeModel eventStreamFrom(ShapeModel shapeModel) {
if (shapeModel == null || shapeModel.getMembers() == null) {
Expand All @@ -82,17 +84,15 @@ private static ShapeModel eventStreamFrom(ShapeModel shapeModel) {
*
* @param model Intermediate model
* @param eventShape shape with "event: true" trait
* @return the event stream shape (eventstream: true) that contains the given event.
* @return the event stream shape (eventstream: true) that contains the given event, or an empty optional if the C2J shape
* is marked as an event but the intermediate model representation is not used by an event stream
*/
public static ShapeModel getBaseEventStreamShape(IntermediateModel model, ShapeModel eventShape) {
public static Optional<ShapeModel> getBaseEventStreamShape(IntermediateModel model, ShapeModel eventShape) {
return model.getShapes().values()
.stream()
.filter(ShapeModel::isEventStream)
.filter(s -> s.getMembers().stream().anyMatch(m -> m.getShape().equals(eventShape)))
.findFirst()
.orElseThrow(() -> new IllegalStateException(
String.format("Event shape %s not referenced in model by any eventstream shape",
eventShape.getC2jName())));
.findFirst();
}

/**
Expand Down Expand Up @@ -152,14 +152,11 @@ public static boolean doesShapeContainsEventStream(ShapeModel parentShape, Shape
* Returns true if the given event shape is a sub-member of any operation request.
*/
public static boolean isRequestEvent(IntermediateModel model, ShapeModel eventShape) {
try {
ShapeModel eventStreamShape = getBaseEventStreamShape(model, eventShape);
return model.getOperations().values()
.stream()
.anyMatch(o -> doesShapeContainsEventStream(o.getInputShape(), eventStreamShape));
} catch (IllegalStateException e) {
return false;
}
return getBaseEventStreamShape(model, eventShape)
.map(stream -> model.getOperations().values()
.stream()
.anyMatch(o -> doesShapeContainsEventStream(o.getInputShape(), stream)))
.orElse(false);
}

private static boolean operationContainsEventStream(OperationModel opModel, ShapeModel eventStreamShape) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,32 +153,9 @@ public TypeSpec poetSpec() {
}

if (this.shapeModel.isEvent()) {
ShapeModel eventStream = EventStreamUtils.getBaseEventStreamShape(intermediateModel, shapeModel);
ClassName eventStreamClassName = poetExtensions.getModelClassFromShape(eventStream);
Collection<OperationModel> opModels = EventStreamUtils.findOperationsWithEventStream(intermediateModel,
eventStream);

Collection<OperationModel> outputOperations = findOutputEventStreamOperations(opModels, eventStream);

if (!outputOperations.isEmpty()) {
ClassName modelClass = poetExtensions.getModelClass(shapeModel.getShapeName());
specBuilder.addSuperinterface(eventStreamClassName);
for (OperationModel opModel : outputOperations) {
ClassName responseHandlerClass = poetExtensions.eventStreamResponseHandlerType(opModel);
specBuilder.addMethod(acceptMethodSpec(modelClass, responseHandlerClass)
.addAnnotation(Override.class)
.addCode(CodeBlock.builder()
.addStatement("visitor.visit(this)")
.build())
.build());
}

} else if (hasInputStreamOperations(opModels, eventStream)) {
specBuilder.addSuperinterface(eventStreamClassName);
} else {
throw new IllegalArgumentException(shapeModel.getC2jName() + " event shape is not a member in any "
+ "request or response event shape");
}
EventStreamUtils.getBaseEventStreamShape(intermediateModel, shapeModel).ifPresent(
eventStream -> addEventSupport(specBuilder, eventStream)
);
}

if (this.shapeModel.getDocumentation() != null) {
Expand All @@ -189,6 +166,33 @@ public TypeSpec poetSpec() {
}
}

private void addEventSupport(TypeSpec.Builder specBuilder, ShapeModel eventStream) {
ClassName eventStreamClassName = poetExtensions.getModelClassFromShape(eventStream);
Collection<OperationModel> opModels = EventStreamUtils.findOperationsWithEventStream(intermediateModel,
eventStream);

Collection<OperationModel> outputOperations = findOutputEventStreamOperations(opModels, eventStream);

if (!outputOperations.isEmpty()) {
ClassName modelClass = poetExtensions.getModelClass(shapeModel.getShapeName());
specBuilder.addSuperinterface(eventStreamClassName);
for (OperationModel opModel : outputOperations) {
ClassName responseHandlerClass = poetExtensions.eventStreamResponseHandlerType(opModel);
specBuilder.addMethod(acceptMethodSpec(modelClass, responseHandlerClass)
.addAnnotation(Override.class)
.addCode(CodeBlock.builder()
.addStatement("visitor.visit(this)")
.build())
.build());
}
} else if (hasInputStreamOperations(opModels, eventStream)) {
specBuilder.addSuperinterface(eventStreamClassName);
} else {
throw new IllegalArgumentException(shapeModel.getC2jName() + " event shape is not a member in any "
+ "request or response event shape");
}
}

private boolean hasInputStreamOperations(Collection<OperationModel> opModels, ShapeModel eventStream) {
return opModels.stream()
.anyMatch(op -> EventStreamUtils.doesShapeContainsEventStream(op.getInputShape(), eventStream));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ protected FieldSpec operationInfoField() {
}

private String getMemberNameFromEventStream() {
ShapeModel eventStream = EventStreamUtils.getBaseEventStreamShape(intermediateModel, shapeModel);
ShapeModel eventStream = EventStreamUtils.getBaseEventStreamShape(intermediateModel, shapeModel)
.orElseThrow(() -> new IllegalStateException("Could not find associated event stream spec for "
+ shapeModel.getC2jName()));
return eventStream.getMembers().stream()
.filter(memberModel -> memberModel.getShape().equals(shapeModel))
.findAny()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package software.amazon.awssdk.services.sharedeventstream.model;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import software.amazon.awssdk.annotations.Generated;
import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration;
import software.amazon.awssdk.core.SdkField;
import software.amazon.awssdk.core.SdkPojo;
import software.amazon.awssdk.utils.ToString;
import software.amazon.awssdk.utils.builder.CopyableBuilder;
import software.amazon.awssdk.utils.builder.ToCopyableBuilder;

@Generated("software.amazon.awssdk:codegen")
public final class GetRandomPersonRequest extends SharedEventStreamRequest implements
ToCopyableBuilder<GetRandomPersonRequest.Builder, GetRandomPersonRequest> {
private static final List<SdkField<?>> SDK_FIELDS = Collections.unmodifiableList(Arrays.asList());

private GetRandomPersonRequest(BuilderImpl builder) {
super(builder);
}

@Override
public Builder toBuilder() {
return new BuilderImpl(this);
}

public static Builder builder() {
return new BuilderImpl();
}

public static Class<? extends Builder> serializableBuilderClass() {
return BuilderImpl.class;
}

@Override
public int hashCode() {
int hashCode = 1;
hashCode = 31 * hashCode + super.hashCode();
return hashCode;
}

@Override
public boolean equals(Object obj) {
return super.equals(obj) && equalsBySdkFields(obj);
}

@Override
public boolean equalsBySdkFields(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof GetRandomPersonRequest)) {
return false;
}
return true;
}

/**
* Returns a string representation of this object. This is useful for testing and debugging. Sensitive data will be
* redacted from this string using a placeholder value.
*/
@Override
public String toString() {
return ToString.builder("GetRandomPersonRequest").build();
}

public <T> Optional<T> getValueForField(String fieldName, Class<T> clazz) {
return Optional.empty();
}

@Override
public List<SdkField<?>> sdkFields() {
return SDK_FIELDS;
}

public interface Builder extends SharedEventStreamRequest.Builder, SdkPojo, CopyableBuilder<Builder, GetRandomPersonRequest> {
@Override
Builder overrideConfiguration(AwsRequestOverrideConfiguration overrideConfiguration);

@Override
Builder overrideConfiguration(Consumer<AwsRequestOverrideConfiguration.Builder> builderConsumer);
}

static final class BuilderImpl extends SharedEventStreamRequest.BuilderImpl implements Builder {
private BuilderImpl() {
}

private BuilderImpl(GetRandomPersonRequest model) {
super(model);
}

@Override
public Builder overrideConfiguration(AwsRequestOverrideConfiguration overrideConfiguration) {
super.overrideConfiguration(overrideConfiguration);
return this;
}

@Override
public Builder overrideConfiguration(Consumer<AwsRequestOverrideConfiguration.Builder> builderConsumer) {
super.overrideConfiguration(builderConsumer);
return this;
}

@Override
public GetRandomPersonRequest build() {
return new GetRandomPersonRequest(this);
}

@Override
public List<SdkField<?>> sdkFields() {
return SDK_FIELDS;
}
}
}
Loading