Skip to content

Commit 22d7938

Browse files
committed
Add processor to detect shared events + customization to duplicate.
1 parent 757f4de commit 22d7938

File tree

17 files changed

+368
-16
lines changed

17 files changed

+368
-16
lines changed

codegen/src/main/java/software/amazon/awssdk/codegen/customization/processors/DefaultCustomizationProcessor.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public static CodegenCustomizationProcessor getProcessorFor(
3838
new RemoveExceptionMessagePropertyProcessor(),
3939
new UseLegacyEventGenerationSchemeProcessor(),
4040
new NewAndLegacyEventStreamProcessor(),
41+
new EventStreamSharedEventProcessor(config.getDuplicateAndRenameSharedEvents()),
4142
new S3RemoveBucketFromUriProcessor(),
4243
new S3ControlRemoveAccountIdHostPrefixProcessor(),
4344
new ExplicitStringPayloadQueryProtocolProcessor(),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.codegen.customization.processors;
17+
18+
import java.util.HashMap;
19+
import java.util.Map;
20+
import org.slf4j.Logger;
21+
import org.slf4j.LoggerFactory;
22+
import software.amazon.awssdk.codegen.customization.CodegenCustomizationProcessor;
23+
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
24+
import software.amazon.awssdk.codegen.model.intermediate.ShapeModel;
25+
import software.amazon.awssdk.codegen.model.service.Member;
26+
import software.amazon.awssdk.codegen.model.service.ServiceModel;
27+
import software.amazon.awssdk.codegen.model.service.Shape;
28+
29+
/**
30+
* Processor for eventstreams with shared events. This Processor does two things: 1. Apply the duplicateAndRenameSharedEvents
31+
* customization 2. Raise helpful error messages on untransfromed shared events.
32+
*/
33+
public final class EventStreamSharedEventProcessor implements CodegenCustomizationProcessor {
34+
private static final Logger log = LoggerFactory.getLogger(EventStreamSharedEventProcessor.class);
35+
36+
private final Map<String, Map<String, String>> duplicateAndRenameSharedEvents;
37+
38+
public EventStreamSharedEventProcessor(Map<String, Map<String, String>> duplicateAndRenameSharedEvents) {
39+
this.duplicateAndRenameSharedEvents = duplicateAndRenameSharedEvents;
40+
}
41+
42+
@Override
43+
public void preprocess(ServiceModel serviceModel) {
44+
if (duplicateAndRenameSharedEvents == null || duplicateAndRenameSharedEvents.isEmpty()) {
45+
return;
46+
}
47+
48+
for (Map.Entry<String, Map<String, String>> eventStreamEntry : duplicateAndRenameSharedEvents.entrySet()) {
49+
50+
String eventStreamName = eventStreamEntry.getKey();
51+
Shape eventStreamShape = serviceModel.getShapes().get(eventStreamName);
52+
53+
validateIsEventStream(eventStreamShape, eventStreamName);
54+
55+
Map<String, Member> eventStreamMembers = eventStreamShape.getMembers();
56+
for (Map.Entry<String, String> eventEntry : eventStreamEntry.getValue().entrySet()) {
57+
Member eventMemberToModify = eventStreamMembers.get(eventEntry.getKey());
58+
59+
if (eventMemberToModify == null) {
60+
throw new IllegalStateException(
61+
String.format("Cannot find event member [%s] in the eventstream [%s] when processing "
62+
+ "customization config duplicateAndRenameSharedEvents.%s",
63+
eventEntry.getKey(), eventStreamName, eventStreamName));
64+
}
65+
66+
String shapeToDuplicate = eventMemberToModify.getShape();
67+
Shape eventMemberShape = serviceModel.getShape(shapeToDuplicate);
68+
69+
if (eventMemberShape == null || !eventMemberShape.isEvent()) {
70+
throw new IllegalStateException(
71+
String.format("Error: %s must be an Event shape when processing "
72+
+ "customization config duplicateAndRenameSharedEvents.%s",
73+
eventEntry.getKey(), eventStreamName));
74+
}
75+
76+
String newShapeName = eventEntry.getValue();
77+
if (serviceModel.getShapes().containsKey(newShapeName)) {
78+
throw new IllegalStateException(
79+
String.format("Error: %s is already in the model when processing "
80+
+ "customization config duplicateAndRenameSharedEvents.%s",
81+
newShapeName, eventStreamName));
82+
}
83+
serviceModel.getShapes().put(newShapeName, eventMemberShape);
84+
eventMemberToModify.setShape(newShapeName);
85+
log.info("Duplicated and renamed event member on {} from {} -> {}",
86+
eventStreamName, shapeToDuplicate, newShapeName);
87+
}
88+
}
89+
}
90+
91+
private static void validateIsEventStream(Shape shape, String name) {
92+
if (shape == null) {
93+
throw new IllegalStateException(
94+
String.format("Cannot find eventstream shape [%s] in the model when processing "
95+
+ "customization config duplicateAndRenameSharedEvents.%s", name, name));
96+
}
97+
if (!shape.isEventstream()) {
98+
throw new IllegalStateException(
99+
String.format("Error: %s must be an EventStream when processing "
100+
+ "customization config duplicateAndRenameSharedEvents.%s", name, name));
101+
}
102+
}
103+
104+
@Override
105+
public void postprocess(IntermediateModel intermediateModel) {
106+
// validate that there are no events shared between multiple eventstreams.
107+
// events may be used multiple times in the same eventstream.
108+
Map<String, String> seenEvents = new HashMap<>();
109+
110+
for (ShapeModel shapeModel : intermediateModel.getShapes().values()) {
111+
if (shapeModel.isEventStream()) {
112+
shapeModel.getMembers().forEach(m -> {
113+
ShapeModel memberShape = intermediateModel.getShapes().get(m.getC2jShape());
114+
if (memberShape.isEvent()) {
115+
if (seenEvents.containsKey(memberShape.getShapeName())
116+
&& !seenEvents.get(memberShape.getShapeName()).equals(shapeModel.getShapeName())) {
117+
throw new IllegalStateException(
118+
String.format("Event %s is shared between multiple EventStreams. Apply the "
119+
+ "duplicateAndRenameSharedEvents customization to resolve the issue.",
120+
memberShape.getShapeName()));
121+
}
122+
seenEvents.put(memberShape.getShapeName(), shapeModel.getShapeName());
123+
}
124+
});
125+
}
126+
}
127+
}
128+
}

codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/CustomizationConfig.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,12 @@ public class CustomizationConfig {
210210
*/
211211
private Map<String, List<String>> useLegacyEventGenerationScheme = new HashMap<>();
212212

213+
/**
214+
* Customization to instruct the code generator to duplicate and rename an event that is shared
215+
* by multiple EventStreams.
216+
*/
217+
private Map<String, Map<String, String>> duplicateAndRenameSharedEvents = new HashMap<>();
218+
213219
/**
214220
* How the code generator should behave when it encounters shapes with underscores in the name.
215221
*/
@@ -654,6 +660,14 @@ public void setUseLegacyEventGenerationScheme(Map<String, List<String>> useLegac
654660
this.useLegacyEventGenerationScheme = useLegacyEventGenerationScheme;
655661
}
656662

663+
public Map<String, Map<String, String>> getDuplicateAndRenameSharedEvents() {
664+
return duplicateAndRenameSharedEvents;
665+
}
666+
667+
public void setDuplicateAndRenameSharedEvents(Map<String, Map<String, String>> duplicateAndRenameSharedEvents) {
668+
this.duplicateAndRenameSharedEvents = duplicateAndRenameSharedEvents;
669+
}
670+
657671
public UnderscoresInNameBehavior getUnderscoresInNameBehavior() {
658672
return underscoresInNameBehavior;
659673
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.codegen.customization.processors;
17+
18+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
19+
import static org.junit.jupiter.api.Assertions.assertEquals;
20+
import static org.junit.jupiter.api.Assertions.assertNotNull;
21+
22+
import java.io.File;
23+
import org.junit.jupiter.api.Test;
24+
import software.amazon.awssdk.codegen.C2jModels;
25+
import software.amazon.awssdk.codegen.IntermediateModelBuilder;
26+
import software.amazon.awssdk.codegen.model.config.customization.CustomizationConfig;
27+
import software.amazon.awssdk.codegen.model.service.ServiceModel;
28+
import software.amazon.awssdk.codegen.model.service.Shape;
29+
import software.amazon.awssdk.codegen.utils.ModelLoaderUtils;
30+
31+
public class EventStreamSharedEventProcessorTest {
32+
private static final String RESOURCE_ROOT = "/software/amazon/awssdk/codegen/customization/processors"
33+
+ "/eventstreamsharedeventprocessor/";
34+
35+
@Test
36+
public void duplicatesAndRenamesSharedEvent() {
37+
File serviceModelFile =
38+
new File(EventStreamSharedEventProcessorTest.class.getResource(RESOURCE_ROOT + "service-2.json").getFile());
39+
ServiceModel serviceModel = ModelLoaderUtils.loadModel(ServiceModel.class, serviceModelFile);
40+
File customizationConfigFile =
41+
new File(EventStreamSharedEventProcessorTest.class.getResource(RESOURCE_ROOT + "customization.config").getFile());
42+
CustomizationConfig config = ModelLoaderUtils.loadModel(CustomizationConfig.class, customizationConfigFile);
43+
44+
EventStreamSharedEventProcessor processor =
45+
new EventStreamSharedEventProcessor(config.getDuplicateAndRenameSharedEvents());
46+
processor.preprocess(serviceModel);
47+
48+
Shape newEventShape = serviceModel.getShape("PayloadB");
49+
assertNotNull(newEventShape);
50+
assertEquals(serviceModel.getShape("Payload"), newEventShape);
51+
52+
Shape streamB = serviceModel.getShape("StreamB");
53+
assertEquals("PayloadB", streamB.getMembers().get("Payload").getShape());
54+
}
55+
56+
@Test
57+
public void modelWithSharedEvents_raises() {
58+
File serviceModelFile =
59+
new File(EventStreamSharedEventProcessorTest.class.getResource(RESOURCE_ROOT + "service-2.json").getFile());
60+
ServiceModel serviceModel = ModelLoaderUtils.loadModel(ServiceModel.class, serviceModelFile);
61+
62+
CustomizationConfig emptyConfig = CustomizationConfig.create();
63+
64+
assertThatThrownBy(() -> new IntermediateModelBuilder(
65+
C2jModels.builder()
66+
.serviceModel(serviceModel)
67+
.customizationConfig(emptyConfig)
68+
.build()).build())
69+
.isInstanceOf(IllegalStateException.class)
70+
.hasMessageContaining("Event Payload is shared between multiple EventStreams");
71+
}
72+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"duplicateAndRenameSharedEvents": {
3+
"StreamB": {
4+
"Payload": "PayloadB"
5+
}
6+
}
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
{
2+
"version": "2.0",
3+
"metadata": {
4+
"apiVersion": "2010-05-08",
5+
"endpointPrefix": "shared-event-stream-service",
6+
"globalEndpoint": "shared-event-stream.amazonaws.com",
7+
"protocol": "rest-json",
8+
"serviceAbbreviation": "Shared Event Stream Service",
9+
"serviceFullName": "Service that shares event streams",
10+
"serviceId":"Shared Event Stream Service",
11+
"signatureVersion": "v4",
12+
"uid": "shared-event-stream-service-2010-05-08",
13+
"xmlNamespace": "https://shared-event-stream-service.amazonaws.com/doc/2010-05-08/"
14+
},
15+
"operations": {
16+
"StreamAOperation" : {
17+
"name": "StreamAOperation",
18+
"http": {
19+
"method": "GET",
20+
"requestUri": "/stream-a"
21+
},
22+
"output": {
23+
"shape": "StreamAOutput"
24+
}
25+
},
26+
"StreamBOperation" : {
27+
"name": "StreamBOperation",
28+
"http": {
29+
"method": "GET",
30+
"requestUri": "/stream-b"
31+
},
32+
"output": {
33+
"shape": "StreamBOutput"
34+
}
35+
}
36+
},
37+
"shapes": {
38+
"String": {
39+
"type": "string"
40+
},
41+
"StreamAOutput": {
42+
"type": "structure",
43+
"members": {
44+
"EventStream": {
45+
"shape": "StreamA"
46+
}
47+
}
48+
},
49+
"StreamA": {
50+
"type": "structure",
51+
"members": {
52+
"Payload": {
53+
"shape": "Payload"
54+
}
55+
},
56+
"eventstream": true
57+
},
58+
"StreamBOutput": {
59+
"type": "structure",
60+
"members": {
61+
"EventStream": {
62+
"shape": "StreamB"
63+
}
64+
}
65+
},
66+
"StreamB": {
67+
"type": "structure",
68+
"members": {
69+
"Payload": {
70+
"shape": "Payload"
71+
}
72+
},
73+
"eventstream": true
74+
},
75+
"Payload": {
76+
"type": "structure",
77+
"members": {
78+
"chunk": {
79+
"shape": "String"
80+
}
81+
},
82+
"event": true
83+
}
84+
},
85+
"documentation": "A service that streams births and deaths"
86+
}

codegen/src/test/resources/software/amazon/awssdk/codegen/emitters/customizations/processors/uselegacyeventgenerationscheme/service-2.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,14 +452,23 @@
452452
"type": "structure",
453453
"members": {
454454
"InputEventOne": {
455-
"shape": "InputEvent"
455+
"shape": "InputEventOne"
456456
},
457457
"InputEventTwo": {
458458
"shape": "InputEventTwo"
459459
}
460460
},
461461
"eventstream": true
462462
},
463+
"InputEventOne": {
464+
"type": "structure",
465+
"members": {
466+
"Foo": {
467+
"shape": "String"
468+
}
469+
},
470+
"event": true
471+
},
463472
"InputEventTwo": {
464473
"type": "structure",
465474
"members": {

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/json/service-2.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -491,14 +491,24 @@
491491
"type": "structure",
492492
"members": {
493493
"InputEventOne": {
494-
"shape": "InputEvent"
494+
"shape": "InputEventOne"
495495
},
496496
"InputEventTwo": {
497497
"shape": "InputEventTwo"
498498
}
499499
},
500500
"eventstream": true
501501
},
502+
"InputEventOne": {
503+
"type": "structure",
504+
"members": {
505+
"ExplicitPayloadMember": {
506+
"shape":"ExplicitPayloadMember",
507+
"eventpayload":true
508+
}
509+
},
510+
"event": true
511+
},
502512
"InputEventTwo": {
503513
"type": "structure",
504514
"members": {

0 commit comments

Comments
 (0)