Skip to content

Duplicate all eventstream event shapes + add new legacy event modes #6052

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

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ private List<IntermediateModelShapeProcessor> createShapeProcessors() {

public IntermediateModel build() {
CodegenCustomizationProcessor customization = DefaultCustomizationProcessor
.getProcessorFor(customConfig);
.getProcessorFor(customConfig, namingStrategy);

customization.preprocess(service);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@
import software.amazon.awssdk.codegen.customization.CodegenCustomizationProcessor;
import software.amazon.awssdk.codegen.customization.CodegenCustomizationProcessorChain;
import software.amazon.awssdk.codegen.model.config.customization.CustomizationConfig;
import software.amazon.awssdk.codegen.naming.NamingStrategy;

public final class DefaultCustomizationProcessor {

private DefaultCustomizationProcessor() {
}

public static CodegenCustomizationProcessor getProcessorFor(
CustomizationConfig config) {
CustomizationConfig config, NamingStrategy namingStrategy) {

return new CodegenCustomizationProcessorChain(
new MetadataModifiersProcessor(config.getCustomServiceMetadata()),
Expand All @@ -37,6 +38,7 @@ public static CodegenCustomizationProcessor getProcessorFor(
new SmithyRpcV2CborProtocolProcessor(),
new RemoveExceptionMessagePropertyProcessor(),
new UseLegacyEventGenerationSchemeProcessor(),
new EventStreamUniqueEventShapesProcessor(config.getUseLegacyEventGenerationScheme(), namingStrategy),
new NewAndLegacyEventStreamProcessor(),
new S3RemoveBucketFromUriProcessor(),
new S3ControlRemoveAccountIdHostPrefixProcessor(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* 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.customization.processors;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import software.amazon.awssdk.codegen.customization.CodegenCustomizationProcessor;
import software.amazon.awssdk.codegen.model.config.customization.LegacyEventGenerationMode;
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
import software.amazon.awssdk.codegen.model.service.ServiceModel;
import software.amazon.awssdk.codegen.model.service.Shape;
import software.amazon.awssdk.codegen.naming.NamingStrategy;
import software.amazon.awssdk.utils.Logger;

/**
* Processor for eventstreams that ensures that all eventstream event shapes are unique - for each eventstream/event it creates a
* new shape with a unique name constructed from the EventStream and Event shape names: `[ShapeName][EventStreamName]`. Any legacy
* eventstream/events (configured with the useLegacyEventGenerationScheme customization) are skipped. When an event shape is
* shared between multiple eventstreams, it causes SDK generation/compilation failures. The top level shape POJO implements the
* event stream interface for each stream and the return type of the sdkEventType method conflicts.
*/
public final class EventStreamUniqueEventShapesProcessor implements CodegenCustomizationProcessor {
private static final Logger log = Logger.loggerFor(EventStreamUniqueEventShapesProcessor.class);

private final Map<String, Map<String, LegacyEventGenerationMode>> useLegacyEventGenerationScheme;
private final NamingStrategy namingStrategy;

public EventStreamUniqueEventShapesProcessor(
Map<String, Map<String, LegacyEventGenerationMode>> useLegacyEventGenerationScheme,
NamingStrategy namingStrategy) {
this.useLegacyEventGenerationScheme = useLegacyEventGenerationScheme;
this.namingStrategy = namingStrategy;
}

@Override
public void preprocess(ServiceModel serviceModel) {
Map<String, Shape> newEventShapes = new HashMap<>();
serviceModel.getShapes().forEach((name, shape) -> {
if (!shape.isEventstream()) {
return;
}

preprocessEventStream(serviceModel, name, shape, newEventShapes);
});
serviceModel.getShapes().putAll(newEventShapes);
}

private void preprocessEventStream(ServiceModel serviceModel, String eventStreamName, Shape eventStreamShape, Map<String,
Shape> newEventShapes) {
Map<String, LegacyEventGenerationMode> eventLegacyModes = useLegacyEventGenerationScheme
.getOrDefault(eventStreamName, Collections.emptyMap());

eventStreamShape.getMembers().forEach((memberName, member) -> {
String eventShapeName = member.getShape();
Shape memberTargetShape = serviceModel.getShape(eventShapeName);
LegacyEventGenerationMode legacyEventGenerationMode = eventLegacyModes
.getOrDefault(memberName, LegacyEventGenerationMode.DISABLED);

if (memberTargetShape.isEvent() && legacyEventGenerationMode == LegacyEventGenerationMode.DISABLED) {
String newShapeName = namingStrategy.getUniqueEventStreamEventShapeName(member, eventStreamName);
if (serviceModel.getShapes().containsKey(newShapeName)) {
log.warn(() -> String.format("Shape name conflict, unable to create a new unique event shape name for %s in"
+ " eventstream %s because %s already exists in the model. Skipping.",
eventShapeName, eventStreamName, newShapeName));
} else {
log.debug(() -> String.format("Creating new, unique, event shape for %s in eventstream %s: %s",
eventShapeName, eventStreamName, newShapeName));
newEventShapes.put(newShapeName, memberTargetShape);
member.setShape(newShapeName);
}
}
});
}

@Override
public void postprocess(IntermediateModel intermediateModel) {
// no-op
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
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.LegacyEventGenerationMode;
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
import software.amazon.awssdk.codegen.model.intermediate.MemberModel;
import software.amazon.awssdk.codegen.model.intermediate.ShapeModel;
Expand All @@ -42,9 +42,8 @@ public void preprocess(ServiceModel serviceModel) {

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

Map<String, Map<String, LegacyEventGenerationMode>> useLegacyEventGenerationScheme =
intermediateModel.getCustomizationConfig().getUseLegacyEventGenerationScheme();
useLegacyEventGenerationScheme.forEach((eventStream, members) -> {
ShapeModel shapeModel = getShapeByC2jName(intermediateModel, eventStream);

Expand All @@ -56,13 +55,15 @@ public void postprocess(IntermediateModel intermediateModel) {

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

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

if (event != null) {
String shapeName = event.getC2jShape();
int count = shapeToEventCount.getOrDefault(shapeName, 0);
shapeToEventCount.put(shapeName, ++count);
if (legacyEventGenerationMode == LegacyEventGenerationMode.NO_EVENT_SUBCLASS) {
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);
Expand All @@ -73,7 +74,7 @@ public void postprocess(IntermediateModel intermediateModel) {
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);
+ eventStream + ", but it targets more than one member with the shape " + shape);
}
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ 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<>();
private Map<String, 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 +646,12 @@ public void setAllowEndpointOverrideForEndpointDiscoveryRequiredOperations(
allowEndpointOverrideForEndpointDiscoveryRequiredOperations;
}

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

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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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;

/**
* Legacy event generation modes.
*/
public enum LegacyEventGenerationMode {
DISABLED,
NO_EVENT_SUBCLASS, // old legacy - do not generate subclasses of events
NO_UNIQUE_EVENT_NAMES // new legacy - do not duplicate event shapes to ensure unique events
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
import software.amazon.awssdk.codegen.model.intermediate.MemberModel;
import software.amazon.awssdk.codegen.model.intermediate.Metadata;
import software.amazon.awssdk.codegen.model.service.Member;
import software.amazon.awssdk.codegen.model.service.ServiceModel;
import software.amazon.awssdk.codegen.model.service.Shape;
import software.amazon.awssdk.utils.Logger;
Expand Down Expand Up @@ -411,6 +412,12 @@ public String getUnionEnumTypeName(MemberModel memberModel) {
return screamCase(memberModel.getName());
}

@Override
public String getUniqueEventStreamEventShapeName(Member eventMember, String eventStreamName) {
return eventMember.getShape() + eventStreamName;
}


private String rewriteInvalidMemberName(String memberName, Shape parentShape) {
if (isJavaKeyword(memberName) || isDisallowedNameForShape(memberName, parentShape)) {
return Utils.unCapitalize(memberName + CONFLICTING_NAME_SUFFIX);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
import software.amazon.awssdk.codegen.model.intermediate.MemberModel;
import software.amazon.awssdk.codegen.model.service.Member;
import software.amazon.awssdk.codegen.model.service.Shape;
import software.amazon.awssdk.core.SdkField;

Expand Down Expand Up @@ -200,6 +201,15 @@ public interface NamingStrategy {
*/
String getExistenceCheckMethodName(String memberName, Shape parentShape);

/**
* Returns a unique shape name to use for an event member of an eventStream.
*
* @param eventMember The event member to get the shape name for.
* @param eventStreamName The name of the eventStream containing the member.
* @return Unique name for the event shape / eventStream combo.
*/
String getUniqueEventStreamEventShapeName(Member eventMember, String eventStreamName);

/**
* Verify the customer-visible naming in the provided intermediate model will compile and is idiomatic to Java.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@

import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.TypeSpec;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.lang.model.element.Modifier;
import software.amazon.awssdk.codegen.model.config.customization.LegacyEventGenerationMode;
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
import software.amazon.awssdk.codegen.model.intermediate.MemberModel;
import software.amazon.awssdk.codegen.model.intermediate.ShapeModel;
Expand Down Expand Up @@ -56,16 +56,16 @@ public String eventPackageName() {
}

public boolean useLegacyGenerationScheme(MemberModel event) {
Map<String, List<String>> useLegacyEventGenerationScheme = intermediateModel.getCustomizationConfig()
.getUseLegacyEventGenerationScheme();
Map<String, Map<String, LegacyEventGenerationMode>> useLegacyEventGenerationScheme =
intermediateModel.getCustomizationConfig().getUseLegacyEventGenerationScheme();

List<String> targetEvents = useLegacyEventGenerationScheme.get(eventStream.getC2jName());
Map<String, LegacyEventGenerationMode> targetEvents = useLegacyEventGenerationScheme.get(eventStream.getC2jName());

if (targetEvents == null) {
if (targetEvents == null || !targetEvents.containsKey(event.getC2jName())) {
return false;
}

return targetEvents.stream().anyMatch(e -> e.equals(event.getC2jName()));
return targetEvents.get(event.getC2jName()).equals(LegacyEventGenerationMode.NO_EVENT_SUBCLASS);
}

public ClassName eventClassName(MemberModel eventModel) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@


import java.io.File;
import org.junit.BeforeClass;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
Expand All @@ -39,11 +39,11 @@ public class UseLegacyEventGenerationSchemeProcessorTest {
@Rule
public ExpectedException thrown = ExpectedException.none();

private static ServiceModel serviceModel;
private ServiceModel serviceModel;


@BeforeClass
public static void setup() {
@Before
public void setup() {
String c2jFilePath = UseLegacyEventGenerationSchemeProcessorTest.class.getResource(RESOURCE_ROOT + "/service-2.json").getFile();
File c2jFile = new File(c2jFilePath);

Expand All @@ -67,7 +67,7 @@ public void testPostProcess_customizationIsValid_succeeds() {
}


private static IntermediateModel intermediateModelWithConfig(String configName) {
private IntermediateModel intermediateModelWithConfig(String configName) {
return new IntermediateModelBuilder(C2jModels.builder()
.serviceModel(serviceModel)
.customizationConfig(loadCustomizationConfig(configName))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"useLegacyEventGenerationScheme": {
"EventStream": ["EventOne"]
"EventStream": {"EventOne": "NO_EVENT_SUBCLASS"}
},
"underscoresInNameBehavior": "ALLOW"
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"useLegacyEventGenerationScheme": {
"EventStream": ["EventOne", "secondEventOne"]
}
"EventStream": {"EventOne": "NO_EVENT_SUBCLASS", "secondEventOne": "NO_EVENT_SUBCLASS"}
},
"underscoresInNameBehavior": "ALLOW"
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,17 @@
"createMethodParams": ["param1", "param2", "param3"]
},
"useLegacyEventGenerationScheme": {
"EventStream": ["EventOne", "event-two", "eventThree"]
"InputEventStream": {"InputEvent": "NO_UNIQUE_EVENT_NAMES"},
"InputEventStreamTwo": {
"InputEventOne": "NO_UNIQUE_EVENT_NAMES",
"InputEventTwo": "NO_UNIQUE_EVENT_NAMES"
},
"EventStream": {
"EventOne": "NO_EVENT_SUBCLASS",
"eventThree": "NO_EVENT_SUBCLASS",
"EventTheSecond": "NO_UNIQUE_EVENT_NAMES",
"secondEventOne": "NO_UNIQUE_EVENT_NAMES"
}
},
"customServiceMetadata": {
"protocol": "cbor"
Expand Down
Loading
Loading