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? 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

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
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
@@ -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;

@@ -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?
});
}

Original file line number Diff line number Diff line change
@@ -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;
@@ -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());
Original file line number Diff line number Diff line change
@@ -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;
@@ -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
@@ -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.
@@ -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;
}

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
@@ -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);
}

Original file line number Diff line number Diff line change
@@ -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;
@@ -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);
@@ -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) {
@@ -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
@@ -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());
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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;
@@ -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)
@@ -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() {
Loading