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

Draft
wants to merge 18 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
6 changes: 6 additions & 0 deletions .changes/next-release/feature-AWSSDKforJavav2-c73b965.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "feature",
"category": "AWS SDK for Java v2",
"contributor": "",
"description": "Fixes codegeneration issues for eventstreams with shared event shapes: Duplicate and rename all eventstream event shapes with a unique name + add new disableUniqueEventStreamShapePreprocessing customization config."
}
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,10 @@ public static CodegenCustomizationProcessor getProcessorFor(
new SmithyRpcV2CborProtocolProcessor(),
new RemoveExceptionMessagePropertyProcessor(),
new UseLegacyEventGenerationSchemeProcessor(),
new EventStreamUniqueEventShapesProcessor(
config.getUseLegacyEventGenerationScheme(),
config.getDisableUniqueEventStreamShapePreprocessing(),
namingStrategy),
new NewAndLegacyEventStreamProcessor(),
new S3RemoveBucketFromUriProcessor(),
new S3ControlRemoveAccountIdHostPrefixProcessor(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* 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.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import software.amazon.awssdk.codegen.customization.CodegenCustomizationProcessor;
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, List<String>> useLegacyEventGenerationScheme;
private final Map<String, List<String>> disableUniqueEventStreamShapePreprocessing;
private final NamingStrategy namingStrategy;

public EventStreamUniqueEventShapesProcessor(
Map<String, List<String>> useLegacyEventGenerationScheme,
Map<String, List<String>> disableUniqueEventStreamShapePreprocessing,
NamingStrategy namingStrategy) {
this.useLegacyEventGenerationScheme = useLegacyEventGenerationScheme;
this.disableUniqueEventStreamShapePreprocessing = disableUniqueEventStreamShapePreprocessing;
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) {
Set<String> disableUniqueEventDuplication =
Stream.of(
useLegacyEventGenerationScheme.getOrDefault(eventStreamName, Collections.emptyList()),
disableUniqueEventStreamShapePreprocessing.getOrDefault(eventStreamName, Collections.emptyList())
)
.flatMap(List::stream)
.collect(Collectors.toSet());

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

if (memberTargetShape.isEvent() && !disableUniqueEventDuplication.contains(memberName)) {
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 @@ -210,6 +210,15 @@ public class CustomizationConfig {
*/
private Map<String, List<String>> useLegacyEventGenerationScheme = new HashMap<>();

/**
* Customization to instruct the code generator to not duplicate event shapes to ensure that event stream shapes
* are unique
* <p>
* <b>NOTE</b>This customization is primarily here to preserve backwards compatibility with existing code before the
* EventStreamUniqueEventShapesProcessor was added.
*/
private Map<String, List<String>> disableUniqueEventStreamShapePreprocessing = new HashMap<>();

/**
* How the code generator should behave when it encounters shapes with underscores in the name.
*/
Expand Down Expand Up @@ -654,6 +663,15 @@ public void setUseLegacyEventGenerationScheme(Map<String, List<String>> useLegac
this.useLegacyEventGenerationScheme = useLegacyEventGenerationScheme;
}

public Map<String, List<String>> getDisableUniqueEventStreamShapePreprocessing() {
return disableUniqueEventStreamShapePreprocessing;
}

public void setDisableUniqueEventStreamShapePreprocessing(
Map<String, List<String>> disableUniqueEventStreamShapePreprocessing) {
this.disableUniqueEventStreamShapePreprocessing = disableUniqueEventStreamShapePreprocessing;
}

public UnderscoresInNameBehavior getUnderscoresInNameBehavior() {
return underscoresInNameBehavior;
}
Expand Down
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 eventStreamName + eventMember.getShape();
}


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 @@ -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
Expand Up @@ -21,7 +21,6 @@
import static software.amazon.awssdk.utils.FunctionalUtils.invokeSafely;

import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Locale;
import org.junit.Test;
Expand Down Expand Up @@ -52,15 +51,15 @@ public SharedStreamAwsModelSpecTest(ShapeModel shapeModel) {
}

@Test
public void basicGeneration() throws Exception {
public void basicGeneration() {
assertThat(new AwsServiceModel(intermediateModel, shapeModel), generatesTo(referenceFileForShape()));
}

private String referenceFileForShape() {
return "sharedstream/" + shapeModel.getShapeName().toLowerCase(Locale.ENGLISH) + ".java";
}

private static void setUp() throws IOException {
private static void setUp() {
File serviceModelFile = new File(SharedStreamAwsModelSpecTest.class.getResource("sharedstream/service-2.json").getFile());
ServiceModel serviceModel = ModelLoaderUtils.loadModel(ServiceModel.class, serviceModelFile);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package software.amazon.awssdk.codegen.poet.model;

import static java.util.stream.Collectors.toList;
import static org.hamcrest.MatcherAssert.assertThat;
import static software.amazon.awssdk.codegen.poet.PoetMatchers.generatesTo;
import static software.amazon.awssdk.utils.FunctionalUtils.invokeSafely;
import java.io.File;
import java.util.Collection;
import java.util.Locale;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import software.amazon.awssdk.codegen.C2jModels;
import software.amazon.awssdk.codegen.IntermediateModelBuilder;
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;
import software.amazon.awssdk.codegen.utils.ModelLoaderUtils;

@RunWith(Parameterized.class)
public class SharedStreamEventModelSpecTest {
private static IntermediateModel intermediateModel;

private final MemberModel event;
private final ShapeModel eventStream;

@Parameterized.Parameters(name = "{1}-{0}")
public static Collection<Object[]> data() {
invokeSafely(SharedStreamEventModelSpecTest::setUp);
return intermediateModel.getShapes().values().stream()
.filter(ShapeModel::isEventStream)
.flatMap(eventStream -> eventStream.getMembers().stream()
.filter(m -> m.getShape().isEvent())
.map(e -> new Object[]{e, eventStream}))
.collect(toList());
}

public SharedStreamEventModelSpecTest(MemberModel event, ShapeModel eventStream) {
this.event = event;
this.eventStream = eventStream;
}

@Test
public void basicGeneration() {
assertThat(new EventModelSpec(event, eventStream, intermediateModel), generatesTo(referenceFileForShape()));
}

private String referenceFileForShape() {
String namespacedEventImpl = eventStream.getShapeName() + "/default" + event.getName();
return "sharedstream/" + namespacedEventImpl.toLowerCase(Locale.ENGLISH) + ".java";
}

private static void setUp() {
File serviceModelFile = new File(SharedStreamEventModelSpecTest.class.getResource("sharedstream/service-2.json").getFile());
ServiceModel serviceModel = ModelLoaderUtils.loadModel(ServiceModel.class, serviceModelFile);

intermediateModel = new IntermediateModelBuilder(
C2jModels.builder()
.serviceModel(serviceModel)
.customizationConfig(CustomizationConfig.create())
.build())
.build();
}
}
Loading
Loading