Skip to content

Demo of eventstream with shared events approach 1 - move interface down to specific event impl #2

New issue

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

By clicking “No 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? No Sign in to your account

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,12 @@

package software.amazon.awssdk.codegen.customization.processors;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.codegen.customization.CodegenCustomizationProcessor;
import software.amazon.awssdk.codegen.model.config.customization.CustomizationConfig;
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
import software.amazon.awssdk.codegen.model.intermediate.MemberModel;
import software.amazon.awssdk.codegen.model.intermediate.ShapeModel;
import software.amazon.awssdk.codegen.model.service.ServiceModel;

Expand All @@ -42,40 +40,18 @@ public void preprocess(ServiceModel serviceModel) {

@Override
public void postprocess(IntermediateModel intermediateModel) {
Map<String, List<String>> useLegacyEventGenerationScheme = intermediateModel.getCustomizationConfig()
.getUseLegacyEventGenerationScheme();
Map<String, CustomizationConfig.LegacyEventGenerationMode> useLegacyEventGenerationScheme =
intermediateModel.getCustomizationConfig().getUseLegacyEventGenerationScheme();

useLegacyEventGenerationScheme.forEach((eventStream, members) -> {
ShapeModel shapeModel = getShapeByC2jName(intermediateModel, eventStream);

if (shapeModel == null || !shapeModel.isEventStream()) {
log.warn(String.format("Encountered %s for unrecognized eventstream %s",
CUSTOMIZATION_NAME, eventStream));
return;
}

Map<String, Integer> shapeToEventCount = new HashMap<>();

members.forEach(m -> {
MemberModel event = shapeModel.getMemberByC2jName(m);

if (event != null) {
String shapeName = event.getC2jShape();
int count = shapeToEventCount.getOrDefault(shapeName, 0);
shapeToEventCount.put(shapeName, ++count);
} else {
String msg = String.format("Encountered %s customization for unrecognized eventstream member %s#%s",
CUSTOMIZATION_NAME, eventStream, m);
log.warn(msg);
}
});

shapeToEventCount.forEach((shape, count) -> {
if (count > 1) {
throw new IllegalArgumentException(CUSTOMIZATION_NAME + " customization declared for "
+ eventStream + ", but more than it targets more than one member with the shape " + shape);
}
});
// TODO: Do we need any additional validation here?
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import software.amazon.awssdk.codegen.emitters.GeneratorTask;
import software.amazon.awssdk.codegen.emitters.GeneratorTaskParams;
import software.amazon.awssdk.codegen.emitters.PoetGeneratorTask;
import software.amazon.awssdk.codegen.model.config.customization.CustomizationConfig;
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
import software.amazon.awssdk.codegen.model.intermediate.MemberModel;
import software.amazon.awssdk.codegen.model.intermediate.Metadata;
Expand Down Expand Up @@ -100,7 +101,8 @@ private List<GeneratorTask> eventModelGenerationTasks() {

return eventStream.getMembers().stream()
.filter(e -> e.getShape().isEvent())
.filter(e -> !eventStreamSpecHelper.useLegacyGenerationScheme(e))
.filter(e -> eventStreamSpecHelper.legacyEventGenerationMode()
!= CustomizationConfig.LegacyEventGenerationMode.NO_ES_EVENT_IMPL)
.map(e -> createEventGenerationTask(e, eventStream));
})
.collect(Collectors.toList());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

package software.amazon.awssdk.codegen.model.config.customization;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
Expand All @@ -35,6 +36,12 @@
*/
public class CustomizationConfig {

public enum LegacyEventGenerationMode {
DISABLED,
NO_ES_EVENT_IMPL, // old legacy
TOP_LEVEL_ES_INTERFACE // new legacy
}

/**
* List of 'convenience' overloads to generate for model classes. Convenience overloads expose a
* different type that is adapted to the real type
Expand Down Expand Up @@ -208,7 +215,8 @@ public class CustomizationConfig {
* generation scheme for the visitor methods was changed. There should be no good reason to use this customization
* for any other purpose.
*/
private Map<String, List<String>> useLegacyEventGenerationScheme = new HashMap<>();
@JsonDeserialize(using = UseLegacyEventSchemeDeserializer.class)
private Map<String, LegacyEventGenerationMode> useLegacyEventGenerationScheme = new HashMap<>();

/**
* How the code generator should behave when it encounters shapes with underscores in the name.
Expand Down Expand Up @@ -646,11 +654,11 @@ public void setAllowEndpointOverrideForEndpointDiscoveryRequiredOperations(
allowEndpointOverrideForEndpointDiscoveryRequiredOperations;
}

public Map<String, List<String>> getUseLegacyEventGenerationScheme() {
public Map<String, LegacyEventGenerationMode> getUseLegacyEventGenerationScheme() {
return useLegacyEventGenerationScheme;
}

public void setUseLegacyEventGenerationScheme(Map<String, List<String>> useLegacyEventGenerationScheme) {
public void setUseLegacyEventGenerationScheme(Map<String, LegacyEventGenerationMode> useLegacyEventGenerationScheme) {
this.useLegacyEventGenerationScheme = useLegacyEventGenerationScheme;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package software.amazon.awssdk.codegen.model.config.customization;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;

public class UseLegacyEventSchemeDeserializer
extends JsonDeserializer<Map<String, CustomizationConfig.LegacyEventGenerationMode>> {
@Override
public Map<String, CustomizationConfig.LegacyEventGenerationMode> deserialize(
JsonParser p, DeserializationContext ctxt) throws IOException {

Map<String, CustomizationConfig.LegacyEventGenerationMode> result = new HashMap<>();

JsonNode rootNode = p.getCodec().readTree(p);
Iterator<Map.Entry<String, JsonNode>> fields = rootNode.fields();

while (fields.hasNext()) {
Map.Entry<String, JsonNode> field = fields.next();
String key = field.getKey();
JsonNode valueNode = field.getValue();

if (valueNode.isArray()) {
// Old format: List<String>
result.put(key, CustomizationConfig.LegacyEventGenerationMode.NO_ES_EVENT_IMPL);
} else if (valueNode.isTextual()) {
// New format: Enum name as string
try {
CustomizationConfig.LegacyEventGenerationMode value =
CustomizationConfig.LegacyEventGenerationMode.valueOf(
valueNode.asText().toUpperCase(Locale.US));
result.put(key, value);
} catch (IllegalArgumentException e) {
throw new IOException("Invalid enum value: " + valueNode.asText(), e);
}
} else {
throw new IOException("Unexpected format for key: " + key);
}
}

return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ protected MethodSpec.Builder applyVisitSubTypeMethodSpecUpdates(TypeSpec.Builder
.param("event", "Event being visited")
.build();
return methodBuilder.addModifiers(Modifier.DEFAULT)
.addStatement("visitDefault(event)")
.addStatement("visitDefault(($T)event)", eventStreamBaseClass)
.addJavadoc(javadocs, eventSubType, eventStreamBaseClass);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import javax.lang.model.element.Modifier;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.codegen.docs.DocumentationBuilder;
import software.amazon.awssdk.codegen.model.config.customization.CustomizationConfig;
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
import software.amazon.awssdk.codegen.model.intermediate.MemberModel;
import software.amazon.awssdk.codegen.model.intermediate.OperationModel;
Expand Down Expand Up @@ -214,6 +215,21 @@ private void addEventSupport(TypeSpec.Builder specBuilder) {
}

private void addEventSupport(TypeSpec.Builder specBuilder, ShapeModel eventStream) {
CustomizationConfig.LegacyEventGenerationMode eventGenerationMode = legacyEventGenerationMode(eventStream);
if (eventGenerationMode == CustomizationConfig.LegacyEventGenerationMode.DISABLED) {
// for non-legacy cases we do not add ANY eventstream interfaces/methods on the top level/generic POJO
// TODO: Document why? or fix marshallers
specBuilder.addMethod(MethodSpec.methodBuilder("sdkEventType")
.addModifiers(PUBLIC)
.returns(ParameterizedTypeName.get(
ClassName.get(Enum.class),
WildcardTypeName.subtypeOf(Object.class)
))
.addStatement("throw new $T()", UnsupportedOperationException.class)
.build());
return;
}

ClassName eventStreamClassName = poetExtensions.getModelClassFromShape(eventStream);
Collection<OperationModel> opModels = EventStreamUtils.findOperationsWithEventStream(intermediateModel,
eventStream);
Expand All @@ -225,27 +241,26 @@ private void addEventSupport(TypeSpec.Builder specBuilder, ShapeModel eventStrea

if (!onOutput && !onInput) {
throw new IllegalArgumentException(shapeModel.getC2jName() + " event shape is not a member in any "
+ "request or response event shape");
+ "request or response event shape");
}

EventStreamSpecHelper helper = new EventStreamSpecHelper(eventStream, intermediateModel);

specBuilder.addSuperinterface(eventStreamClassName);

boolean usesLegacyScheme = useLegacyEventGenerationScheme(eventStream);
Optional<MemberModel> legacyEvent = findLegacyGenerationEventWithShape(eventStream);

if (usesLegacyScheme && legacyEvent.isPresent()) {
if (eventGenerationMode == CustomizationConfig.LegacyEventGenerationMode.NO_ES_EVENT_IMPL && legacyEvent.isPresent()) {
NamingStrategy namingStrategy = intermediateModel.getNamingStrategy();
ClassName eventTypeEnum = helper.eventTypeEnumClassName();
specBuilder.addMethod(MethodSpec.methodBuilder("sdkEventType")
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.returns(eventTypeEnum)
.addStatement("return $T.$N",
eventTypeEnum,
namingStrategy.getEnumValueName(legacyEvent.get().getName()))
.build());
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.returns(eventTypeEnum)
.addStatement("return $T.$N",
eventTypeEnum,
namingStrategy.getEnumValueName(legacyEvent.get().getName()))
.build());
}

if (onOutput) {
Expand All @@ -254,9 +269,9 @@ private void addEventSupport(TypeSpec.Builder specBuilder, ShapeModel eventStrea
ClassName responseHandlerClass = poetExtensions.eventStreamResponseHandlerType(opModel);

MethodSpec.Builder acceptMethodSpec = acceptMethodSpec(modelClass, responseHandlerClass)
.addAnnotation(Override.class);
.addAnnotation(Override.class);

if (usesLegacyScheme) {
if (eventGenerationMode == CustomizationConfig.LegacyEventGenerationMode.NO_ES_EVENT_IMPL) {
acceptMethodSpec.addStatement("visitor.visit(this)");
} else {
// The class that represents the event type will be
Expand Down Expand Up @@ -846,7 +861,7 @@ private MethodSpec eventBuilderMethod(MemberModel event) {

ClassName returnType;

if (specHelper.useLegacyGenerationScheme(event)) {
if (legacyEventGenerationMode(shapeModel) == CustomizationConfig.LegacyEventGenerationMode.NO_ES_EVENT_IMPL) {
returnType = eventClassName.nestedClass("Builder");
} else {
ClassName baseClass = poetExtensions.getModelClass(event.getShape().getShapeName());
Expand All @@ -872,25 +887,14 @@ private MethodSpec addModifier(MethodSpec spec, Modifier modifier) {
return spec.toBuilder().addModifiers(modifier).build();
}

private boolean useLegacyEventGenerationScheme(ShapeModel eventStream) {
private CustomizationConfig.LegacyEventGenerationMode legacyEventGenerationMode(ShapeModel eventStream) {
EventStreamSpecHelper helper = new EventStreamSpecHelper(eventStream, intermediateModel);
// This is hacky, but there's no better solution without
// extensive refactoring. We basically need to know if any of
// the *event types* within this even stream have this
// customization enabled, which requires knowing the
// MemberModel that has this given shape.
for (MemberModel member : eventStream.getMembers()) {
if (member.getShape().equals(shapeModel) && helper.useLegacyGenerationScheme(member)) {
return true;
}
}
return false;
return helper.legacyEventGenerationMode();
}

private Optional<MemberModel> findLegacyGenerationEventWithShape(ShapeModel eventStream) {
EventStreamSpecHelper helper = new EventStreamSpecHelper(eventStream, intermediateModel);
for (MemberModel member : eventStream.getMembers()) {
if (member.getShape().equals(shapeModel) && helper.useLegacyGenerationScheme(member)) {
if (member.getShape().equals(shapeModel)) {
return Optional.ofNullable(member);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import java.util.stream.Collectors;
import javax.lang.model.element.Modifier;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.codegen.model.config.customization.CustomizationConfig;
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
import software.amazon.awssdk.codegen.model.intermediate.MemberModel;
import software.amazon.awssdk.codegen.model.intermediate.OperationModel;
Expand Down Expand Up @@ -61,7 +62,9 @@ public EventModelSpec(MemberModel eventModel, ShapeModel eventStream, Intermedia

@Override
public TypeSpec poetSpec() {
return TypeSpec.classBuilder(className())
ClassName eventStreamClassName = poetExtensions.getModelClassFromShape(eventStream);

TypeSpec.Builder builder = TypeSpec.classBuilder(className())
.superclass(baseShapeModelSpec.className())
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addAnnotation(SdkInternalApi.class)
Expand All @@ -73,8 +76,12 @@ public TypeSpec poetSpec() {
.addMethod(builderMethod())
.addMethods(acceptMethods())
.addMethod(sdkEventTypeMethodSpec())
.addTypes(Arrays.asList(builderSpecs.builderInterface(), builderSpecs.beanStyleBuilder()))
.build();
.addTypes(Arrays.asList(builderSpecs.builderInterface(), builderSpecs.beanStyleBuilder()));

if (eventStreamSpecHelper.legacyEventGenerationMode() == CustomizationConfig.LegacyEventGenerationMode.DISABLED) {
builder.addSuperinterface(eventStreamClassName);
}
return builder.build();
}

private CodeBlock classJavadoc() {
Expand Down
Loading