From 4ed120c269f8ca9aad37bd1070c25f12985fea2a Mon Sep 17 00:00:00 2001 From: Hamza JGUERIM Date: Wed, 28 Dec 2022 03:47:26 +0100 Subject: [PATCH] Add Exchange Generation support Signed-off-by: Hamza JGUERIM --- .../csharp/CsharpTemplateStandard.java | 25 +++ .../codegen/csharp/DeploymentSettings.java | 42 +++++ .../codegen/csharp/ExchangeDetail.java | 35 ++++ .../designer/codegen/csharp/FieldDetail.java | 4 + .../designer/codegen/csharp/Template.java | 9 +- .../codegen/csharp/TemplateParameter.java | 12 +- .../csharp/exchange/CoveyParameter.java | 109 ++++++++++++ .../codegen/csharp/exchange/Exchange.java | 78 +++++++++ .../exchange/ExchangeAdapterTemplateData.java | 75 +++++++++ .../ExchangeBootstrapTemplateData.java | 66 ++++++++ .../ExchangeDispatcherTemplateData.java | 70 ++++++++ .../exchange/ExchangeGenerationStep.java | 57 +++++++ .../exchange/ExchangeMapperTemplateData.java | 94 +++++++++++ .../ExchangePropertiesTemplateData.java | 42 +++++ .../csharp/exchange/ExchangeReceiver.java | 83 ++++++++++ .../ExchangeReceiverHolderTemplateData.java | 82 +++++++++ .../codegen/csharp/exchange/ExchangeRole.java | 32 ++++ .../exchange/ExchangeTemplateDataFactory.java | 52 ++++++ .../codegen/csharp/exchange/Formatter.java | 36 ++++ .../csharp/exchange/ProducerExchange.java | 43 +++++ .../codegen/csharp/exchange/Schema.java | 73 +++++++++ .../formatting/AggregateMethodInvocation.java | 15 ++ .../c_sharp/ConsumerExchangeAdapter.ftl | 41 +++++ .../c_sharp/ConsumerExchangeMapper.ftl | 23 +++ .../codegen/c_sharp/ExchangeBootstrap.ftl | 53 ++++++ .../codegen/c_sharp/ExchangeDispatcher.ftl | 64 ++++++++ .../c_sharp/ExchangeReceiverHolder.ftl | 35 ++++ .../c_sharp/ProducerExchangeAdapter.ftl | 40 +++++ .../c_sharp/ProducerExchangeMapper.ftl | 32 ++++ .../CodeGenerationParametersBuilder.java | 155 ++++++++++++++++++ .../csharp/exchange/ContentBuilder.java | 140 ++++++++++++++++ .../exchange/ExchangeGenerationStepTest.java | 94 +++++++++++ .../c_sharp/author-exchange-receivers.text | 73 +++++++++ .../c_sharp/author-producer-adapter.text | 38 +++++ .../c_sharp/book-producer-adapter.text | 38 +++++ .../c_sharp/domain-event-mapper.text | 32 ++++ .../c_sharp/exchange-bootstrap.text | 76 +++++++++ .../c_sharp/exchange-dispatcher.text | 62 +++++++ .../other-aggregate-defined-adapter.text | 39 +++++ .../other-aggregate-defined-mapper.text | 21 +++ .../other-aggregate-removed-adapter.text | 39 +++++ .../other-aggregate-removed-mapper.text | 21 +++ .../other-aggregate-updated-adapter.text | 39 +++++ .../other-aggregate-updated-mapper.text | 21 +++ 44 files changed, 2308 insertions(+), 2 deletions(-) create mode 100644 src/main/java/io/vlingo/xoom/designer/codegen/csharp/DeploymentSettings.java create mode 100644 src/main/java/io/vlingo/xoom/designer/codegen/csharp/ExchangeDetail.java create mode 100644 src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/CoveyParameter.java create mode 100644 src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/Exchange.java create mode 100644 src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeAdapterTemplateData.java create mode 100644 src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeBootstrapTemplateData.java create mode 100644 src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeDispatcherTemplateData.java create mode 100644 src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeGenerationStep.java create mode 100644 src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeMapperTemplateData.java create mode 100644 src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangePropertiesTemplateData.java create mode 100644 src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeReceiver.java create mode 100644 src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeReceiverHolderTemplateData.java create mode 100644 src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeRole.java create mode 100644 src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeTemplateDataFactory.java create mode 100644 src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/Formatter.java create mode 100644 src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ProducerExchange.java create mode 100644 src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/Schema.java create mode 100644 src/main/resources/codegen/c_sharp/ConsumerExchangeAdapter.ftl create mode 100644 src/main/resources/codegen/c_sharp/ConsumerExchangeMapper.ftl create mode 100644 src/main/resources/codegen/c_sharp/ExchangeBootstrap.ftl create mode 100644 src/main/resources/codegen/c_sharp/ExchangeDispatcher.ftl create mode 100644 src/main/resources/codegen/c_sharp/ExchangeReceiverHolder.ftl create mode 100644 src/main/resources/codegen/c_sharp/ProducerExchangeAdapter.ftl create mode 100644 src/main/resources/codegen/c_sharp/ProducerExchangeMapper.ftl create mode 100644 src/test/java/io/vlingo/xoom/designer/codegen/csharp/exchange/CodeGenerationParametersBuilder.java create mode 100644 src/test/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ContentBuilder.java create mode 100644 src/test/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeGenerationStepTest.java create mode 100644 src/test/resources/text-expectations/c_sharp/author-exchange-receivers.text create mode 100644 src/test/resources/text-expectations/c_sharp/author-producer-adapter.text create mode 100644 src/test/resources/text-expectations/c_sharp/book-producer-adapter.text create mode 100644 src/test/resources/text-expectations/c_sharp/domain-event-mapper.text create mode 100644 src/test/resources/text-expectations/c_sharp/exchange-bootstrap.text create mode 100644 src/test/resources/text-expectations/c_sharp/exchange-dispatcher.text create mode 100644 src/test/resources/text-expectations/c_sharp/other-aggregate-defined-adapter.text create mode 100644 src/test/resources/text-expectations/c_sharp/other-aggregate-defined-mapper.text create mode 100644 src/test/resources/text-expectations/c_sharp/other-aggregate-removed-adapter.text create mode 100644 src/test/resources/text-expectations/c_sharp/other-aggregate-removed-mapper.text create mode 100644 src/test/resources/text-expectations/c_sharp/other-aggregate-updated-adapter.text create mode 100644 src/test/resources/text-expectations/c_sharp/other-aggregate-updated-mapper.text diff --git a/src/main/java/io/vlingo/xoom/designer/codegen/csharp/CsharpTemplateStandard.java b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/CsharpTemplateStandard.java index 232826a3..1a5ce1ca 100644 --- a/src/main/java/io/vlingo/xoom/designer/codegen/csharp/CsharpTemplateStandard.java +++ b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/CsharpTemplateStandard.java @@ -10,6 +10,7 @@ import io.vlingo.xoom.codegen.template.TemplateParameters; import io.vlingo.xoom.codegen.template.TemplateStandard; import io.vlingo.xoom.designer.codegen.CodeGenerationProperties; +import io.vlingo.xoom.designer.codegen.csharp.exchange.ExchangeRole; import io.vlingo.xoom.designer.codegen.csharp.projections.ProjectionType; import io.vlingo.xoom.designer.codegen.csharp.storage.Model; import io.vlingo.xoom.designer.codegen.csharp.storage.StorageType; @@ -129,6 +130,30 @@ public enum CsharpTemplateStandard implements TemplateStandard { AUTO_DISPATCH_HANDLERS_MAPPING(parameters -> Template.AUTO_DISPATCH_HANDLERS_MAPPING.filename, (name, parameters) -> name + "ResourceHandlers"), AUTO_DISPATCH_ROUTE(parameters -> Template.AUTO_DISPATCH_ROUTE.filename), + + EXCHANGE_BOOTSTRAP(parameters -> Template.EXCHANGE_BOOTSTRAP.filename, + (name, parameters) -> "ExchangeBootstrap"), + + EXCHANGE_MAPPER(parameters -> parameters.find(EXCHANGE_ROLE).isConsumer() ? + CONSUMER_EXCHANGE_MAPPER.filename : PRODUCER_EXCHANGE_MAPPER.filename, + (name, parameters) -> parameters.find(EXCHANGE_ROLE).isConsumer() ? + parameters.find(LOCAL_TYPE_NAME) + "Mapper" : "DomainEventMapper"), + + EXCHANGE_ADAPTER(parameters -> + parameters.find(EXCHANGE_ROLE).isConsumer() ? + CONSUMER_EXCHANGE_ADAPTER.filename : PRODUCER_EXCHANGE_ADAPTER.filename, + (name, parameters) -> { + final ExchangeRole exchangeRole = parameters.find(EXCHANGE_ROLE); + if(exchangeRole.isConsumer()) { + return parameters.find(LOCAL_TYPE_NAME) + "Adapter"; + } + return parameters.find(AGGREGATE_PROTOCOL_NAME) + exchangeRole.formatName() + "Adapter"; + }), + EXCHANGE_RECEIVER_HOLDER(parameters -> Template.EXCHANGE_RECEIVER_HOLDER.filename, + (name, parameters) -> parameters.find(AGGREGATE_PROTOCOL_NAME) + + "ExchangeReceivers"), + EXCHANGE_DISPATCHER(parameters -> Template.EXCHANGE_DISPATCHER.filename, + (name, parameters) -> "ExchangeDispatcher"), ; private final Function templateFileRetriever; diff --git a/src/main/java/io/vlingo/xoom/designer/codegen/csharp/DeploymentSettings.java b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/DeploymentSettings.java new file mode 100644 index 00000000..66c6e726 --- /dev/null +++ b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/DeploymentSettings.java @@ -0,0 +1,42 @@ +// Copyright © 2012-2022 VLINGO LABS. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. +package io.vlingo.xoom.designer.codegen.csharp; + +import io.vlingo.xoom.designer.codegen.DeploymentType; + +public class DeploymentSettings { + + public final DeploymentType type; + public final String dockerImage; + public final String kubernetesPod; + public final String kubernetesImage; + public final int producerExchangePort; + public final boolean useDocker; + public final boolean useKubernetes; + + public static DeploymentSettings with(final DeploymentType type, + final String dockerImage, + final String kubernetesImage, + final String kubernetesPod, + final int producerExchangePort) { + return new DeploymentSettings(type, dockerImage, kubernetesImage, kubernetesPod, producerExchangePort); + } + + private DeploymentSettings(final DeploymentType type, + final String dockerImage, + final String kubernetesImage, + final String kubernetesPod, + final int producerExchangePort) { + this.type = type; + this.dockerImage = dockerImage; + this.kubernetesPod = kubernetesPod; + this.kubernetesImage = kubernetesImage; + this.useDocker = type.isDocker() || type.isKubernetes(); + this.useKubernetes = type.isKubernetes(); + this.producerExchangePort = producerExchangePort; + } +} diff --git a/src/main/java/io/vlingo/xoom/designer/codegen/csharp/ExchangeDetail.java b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/ExchangeDetail.java new file mode 100644 index 00000000..239866e7 --- /dev/null +++ b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/ExchangeDetail.java @@ -0,0 +1,35 @@ +// Copyright © 2012-2022 VLINGO LABS. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. +package io.vlingo.xoom.designer.codegen.csharp; + +import io.vlingo.xoom.codegen.parameter.CodeGenerationParameter; +import io.vlingo.xoom.designer.codegen.Label; +import io.vlingo.xoom.designer.codegen.csharp.exchange.ExchangeRole; +import io.vlingo.xoom.designer.codegen.csharp.exchange.Schema; + +import java.util.stream.Stream; + +public class ExchangeDetail { + + public static Stream findInvolvedStateFieldsOnReceivers(final CodeGenerationParameter exchange) { + final CodeGenerationParameter aggregate = exchange.parent(); + return exchange.retrieveAllRelated(Label.RECEIVER).flatMap(receiver -> { + final String methodName = receiver.retrieveRelatedValue(Label.MODEL_METHOD); + return AggregateDetail.findInvolvedStateFields(aggregate, methodName); + }); + } + + public static Stream findConsumedQualifiedEventNames(final CodeGenerationParameter exchange) { + if(exchange.retrieveRelatedValue(Label.ROLE, ExchangeRole::of).isProducer()) { + return Stream.empty(); + } + return exchange.retrieveAllRelated(Label.RECEIVER) + .map(receiver -> receiver.retrieveOneRelated(Label.SCHEMA).object()) + .map(Schema::qualifiedName).distinct(); + } + +} diff --git a/src/main/java/io/vlingo/xoom/designer/codegen/csharp/FieldDetail.java b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/FieldDetail.java index 58af8483..f3e3cb3c 100644 --- a/src/main/java/io/vlingo/xoom/designer/codegen/csharp/FieldDetail.java +++ b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/FieldDetail.java @@ -270,4 +270,8 @@ private static Label resolveFieldTypeLabel(final CodeGenerationParameter parent) } throw new IllegalArgumentException("Unable to resolve field type of " + parent.label); } + + public static String genericTypeOf(final CodeGenerationParameter parent, final String fieldName) { + return genericTypeOf(typeOf(parent, fieldName)); + } } diff --git a/src/main/java/io/vlingo/xoom/designer/codegen/csharp/Template.java b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/Template.java index 0e2caf9e..3744c5ec 100644 --- a/src/main/java/io/vlingo/xoom/designer/codegen/csharp/Template.java +++ b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/Template.java @@ -57,7 +57,14 @@ public enum Template { AUTO_DISPATCH_MAPPING("AutoDispatchMapping"), AUTO_DISPATCH_ROUTE("AutoDispatchRoute"), AUTO_DISPATCH_HANDLERS_MAPPING("AutoDispatchHandlersMapping"), - AUTO_DISPATCH_HANDLER_ENTRY("AutoDispatchHandlerEntry"),; + AUTO_DISPATCH_HANDLER_ENTRY("AutoDispatchHandlerEntry"), + EXCHANGE_RECEIVER_HOLDER("ExchangeReceiverHolder"), + EXCHANGE_BOOTSTRAP("ExchangeBootstrap"), + CONSUMER_EXCHANGE_MAPPER("ConsumerExchangeMapper"), + PRODUCER_EXCHANGE_MAPPER("ProducerExchangeMapper"), + CONSUMER_EXCHANGE_ADAPTER("ConsumerExchangeAdapter"), + PRODUCER_EXCHANGE_ADAPTER("ProducerExchangeAdapter"), + EXCHANGE_DISPATCHER("ExchangeDispatcher"),; public final String filename; diff --git a/src/main/java/io/vlingo/xoom/designer/codegen/csharp/TemplateParameter.java b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/TemplateParameter.java index 8bcc77d8..61118a6d 100644 --- a/src/main/java/io/vlingo/xoom/designer/codegen/csharp/TemplateParameter.java +++ b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/TemplateParameter.java @@ -113,7 +113,17 @@ public enum TemplateParameter implements ParameterKey { HANDLER_INDEXES("handlerIndexes"), HANDLER_ENTRIES("handlerEntries"), FACTORY_METHOD("factoryMethod"), - INDEX_NAME("indexName"), ; + INDEX_NAME("indexName"), + EXCHANGE_ROLE("exchangeRole"), + LOCAL_TYPE_NAME("localTypeName"), + SCHEMA_GROUP_NAME("schemaGroupName"), + EXCHANGE_MAPPER_NAME("exchangeMapperName"), + EXCHANGE_ADAPTER_NAME("exchangeAdapterName"), + EXCHANGE_RECEIVERS("exchangeReceivers"), + EXCHANGE_RECEIVER_HOLDER_NAME("exchangeReceiverHolderName"), + PRODUCER_EXCHANGES("producerExchanges"), + EXCHANGES("exchanges"), + INLINE_EXCHANGE_NAMES("inlineExchangeNames"),; public final String key; diff --git a/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/CoveyParameter.java b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/CoveyParameter.java new file mode 100644 index 00000000..7fdfed68 --- /dev/null +++ b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/CoveyParameter.java @@ -0,0 +1,109 @@ +// Copyright © 2012-2022 VLINGO LABS. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +package io.vlingo.xoom.designer.codegen.csharp.exchange; + +import io.vlingo.xoom.codegen.parameter.CodeGenerationParameter; +import io.vlingo.xoom.codegen.template.TemplateParameters; +import io.vlingo.xoom.designer.codegen.Label; +import io.vlingo.xoom.designer.codegen.csharp.CsharpTemplateStandard; +import io.vlingo.xoom.designer.codegen.csharp.TemplateParameter; +import io.vlingo.xoom.lattice.model.IdentifiedDomainEvent; + +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class CoveyParameter { + + public final String localClass; + public final String externalClass; + public final String adapterInstantiation; + public final String receiverInstantiation; + public final String qualifiedLocalClassName; + + public static Set from(final List exchanges) { + final Predicate onlyConsumers = + exchange -> exchange.retrieveRelatedValue(Label.ROLE, ExchangeRole::of).isConsumer(); + + final Predicate onlyProducers = + exchange -> exchange.retrieveRelatedValue(Label.ROLE, ExchangeRole::of).isProducer(); + + final Set consumerCoveys = + exchanges.stream().filter(onlyConsumers) + .flatMap(exchange -> exchange.retrieveAllRelated(Label.RECEIVER)) + .map(receiver -> new CoveyParameter(receiver.parent(Label.EXCHANGE), receiver.retrieveOneRelated(Label.SCHEMA).object())) + .collect(Collectors.toSet()); + + final Set producerCoveys = + exchanges.stream().filter(onlyProducers).map(CoveyParameter::new).collect(Collectors.toSet()); + + return Stream.of(consumerCoveys, producerCoveys).flatMap(Set::stream).collect(Collectors.toSet()); + } + + private CoveyParameter(final CodeGenerationParameter consumerExchange, final Schema schema) { + this.localClass = schema.simpleClassName(); + this.externalClass = String.class.getSimpleName(); + this.adapterInstantiation = resolveConsumerAdapterInstantiation(schema); + this.receiverInstantiation = String.format("new %s(stage)", resolveReceiverName(consumerExchange, schema)); + this.qualifiedLocalClassName = schema.qualifiedName(); + } + + private CoveyParameter(final CodeGenerationParameter producerExchange) { + this.localClass = IdentifiedDomainEvent.class.getSimpleName(); + this.externalClass = IdentifiedDomainEvent.class.getSimpleName(); + this.adapterInstantiation = String.format("new %s()", resolveProducerAdapterName(producerExchange)); + this.receiverInstantiation = "received => {}"; + this.qualifiedLocalClassName = IdentifiedDomainEvent.class.getCanonicalName(); + } + + private String resolveConsumerAdapterInstantiation(final Schema schema) { + return String.format("new %s(\"%s\")", resolveConsumerAdapterName(schema), schema.reference); + } + + private String resolveReceiverName(final CodeGenerationParameter consumerExchange, final Schema schema) { + final TemplateParameters aggregateProtocolName = + TemplateParameters.with(TemplateParameter.AGGREGATE_PROTOCOL_NAME, consumerExchange.parent().value); + + final String holderName = + CsharpTemplateStandard.EXCHANGE_RECEIVER_HOLDER.resolveClassname(aggregateProtocolName); + + return String.format("%s.%s", holderName, schema.innerReceiverClassName()); + } + + private String resolveProducerAdapterName(final CodeGenerationParameter exchange) { + final TemplateParameters parameters = TemplateParameters.with(TemplateParameter.AGGREGATE_PROTOCOL_NAME, exchange.parent().value) + .and(TemplateParameter.EXCHANGE_ROLE, ExchangeRole.PRODUCER); + return CsharpTemplateStandard.EXCHANGE_ADAPTER.resolveClassname(parameters); + } + + private String resolveConsumerAdapterName(final Schema schema) { + final TemplateParameters parameters = TemplateParameters.with(TemplateParameter.LOCAL_TYPE_NAME, schema.simpleClassName()) + .and(TemplateParameter.EXCHANGE_ROLE, ExchangeRole.CONSUMER); + + return CsharpTemplateStandard.EXCHANGE_ADAPTER.resolveClassname(parameters); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CoveyParameter that = (CoveyParameter) o; + return Objects.equals(localClass, that.localClass) && + Objects.equals(adapterInstantiation, that.adapterInstantiation) && + Objects.equals(externalClass, that.externalClass) && + Objects.equals(receiverInstantiation, that.receiverInstantiation); + } + + @Override + public int hashCode() { + return Objects.hash(localClass, adapterInstantiation, externalClass, receiverInstantiation); + } +} diff --git a/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/Exchange.java b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/Exchange.java new file mode 100644 index 00000000..20ab79fc --- /dev/null +++ b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/Exchange.java @@ -0,0 +1,78 @@ +// Copyright © 2012-2022 VLINGO LABS. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +package io.vlingo.xoom.designer.codegen.csharp.exchange; + +import io.vlingo.xoom.codegen.parameter.CodeGenerationParameter; +import io.vlingo.xoom.designer.codegen.Label; +import io.vlingo.xoom.designer.codegen.csharp.CsharpTemplateStandard; + +import java.util.*; +import java.util.stream.Collectors; + +public class Exchange { + + public final String name; + public final String dataObjectName; + public final String variableName; + public final String settingsName; + public final Set coveys; + public final Set roles = new HashSet<>(); + + public static List from(final List aggregates) { + + final Map exchanges = new HashMap<>(); + + final List allExchanges = + aggregates.stream().flatMap(aggregate -> aggregate.retrieveAllRelated(Label.EXCHANGE)) + .collect(Collectors.toList()); + + aggregates.forEach(aggregate -> { + aggregate.retrieveAllRelated(Label.EXCHANGE).forEach(exchangeParam -> { + final ExchangeRole exchangeRole = + exchangeParam.retrieveRelatedValue(Label.ROLE, ExchangeRole::of); + + final Exchange exchange = new Exchange(aggregate.value, exchangeParam, allExchanges); + + exchanges.computeIfAbsent(exchangeParam.value, e -> exchange).addRole(exchangeRole); + }); + }); + + return new ArrayList<>(exchanges.values()); + } + + private Exchange(final String aggregateName, final CodeGenerationParameter exchange, + final List allExchangeParameters) { + this.name = exchange.value; + this.coveys = resolveCoveyParameters(exchange.value, allExchangeParameters); + this.variableName = Formatter.formatExchangeVariableName(exchange.value); + this.settingsName = variableName + "Settings"; + this.dataObjectName = CsharpTemplateStandard.DATA_OBJECT.resolveClassname(aggregateName); + } + + private Set resolveCoveyParameters(final String exchangeName, + final List allExchangeParameters) { + final List relatedExchangeParameters = allExchangeParameters.stream() + .filter(exchange -> exchange.value.equals(exchangeName)) + .collect(Collectors.toList()); + + return CoveyParameter.from(relatedExchangeParameters); + } + + private Exchange addRole(final ExchangeRole exchangeRole) { + this.roles.add(exchangeRole); + return this; + } + + public boolean isProducer() { + return this.roles.contains(ExchangeRole.PRODUCER); + } + + public boolean isConsumer() { + return this.roles.contains(ExchangeRole.CONSUMER); + } +} diff --git a/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeAdapterTemplateData.java b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeAdapterTemplateData.java new file mode 100644 index 00000000..519761c3 --- /dev/null +++ b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeAdapterTemplateData.java @@ -0,0 +1,75 @@ +// Copyright © 2012-2022 VLINGO LABS. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. +package io.vlingo.xoom.designer.codegen.csharp.exchange; + +import io.vlingo.xoom.codegen.content.CodeElementFormatter; +import io.vlingo.xoom.codegen.parameter.CodeGenerationParameter; +import io.vlingo.xoom.codegen.template.TemplateData; +import io.vlingo.xoom.codegen.template.TemplateParameters; +import io.vlingo.xoom.codegen.template.TemplateStandard; +import io.vlingo.xoom.designer.codegen.Label; +import io.vlingo.xoom.designer.codegen.csharp.CsharpTemplateStandard; +import io.vlingo.xoom.designer.codegen.csharp.ExchangeDetail; +import io.vlingo.xoom.designer.codegen.csharp.TemplateParameter; +import io.vlingo.xoom.lattice.model.IdentifiedDomainEvent; +import io.vlingo.xoom.turbo.ComponentRegistry; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class ExchangeAdapterTemplateData extends TemplateData { + + private final TemplateParameters parameters; + + public static List from(final String exchangePackage, + final Stream aggregates) { + final CodeElementFormatter codeElementFormatter = ComponentRegistry.withName("cSharpCodeFormatter"); + return aggregates.flatMap(aggregate -> aggregate.retrieveAllRelated(Label.EXCHANGE)) + .flatMap(exchange -> { + if (exchange.retrieveRelatedValue(Label.ROLE, ExchangeRole::of).isProducer()) { + return Stream.of(new ExchangeAdapterTemplateData(exchangePackage, exchange)); + } else { + return ExchangeDetail.findConsumedQualifiedEventNames(exchange) + .map(qualifiedName -> new ExchangeAdapterTemplateData(codeElementFormatter, exchangePackage, qualifiedName)); + } + }).collect(Collectors.toList()); + } + + private ExchangeAdapterTemplateData(final String exchangePackage, + final CodeGenerationParameter exchange) { + this.parameters = TemplateParameters.with(TemplateParameter.PACKAGE_NAME, exchangePackage) + .and(TemplateParameter.AGGREGATE_PROTOCOL_NAME, exchange.parent().value) + .and(TemplateParameter.SCHEMA_GROUP_NAME, exchange.retrieveRelatedValue(Label.SCHEMA_GROUP)) + .and(TemplateParameter.EXCHANGE_ROLE, ExchangeRole.PRODUCER) + .andResolve(TemplateParameter.EXCHANGE_ADAPTER_NAME, CsharpTemplateStandard.EXCHANGE_ADAPTER::resolveClassname) + .andResolve(TemplateParameter.EXCHANGE_MAPPER_NAME, CsharpTemplateStandard.EXCHANGE_MAPPER::resolveClassname) + .addImport(IdentifiedDomainEvent.class.getCanonicalName()); + } + + private ExchangeAdapterTemplateData(final CodeElementFormatter codeElementFormatter, + final String exchangePackage, + final String qualifiedSchemaName) { + this.parameters = TemplateParameters.with(TemplateParameter.PACKAGE_NAME, exchangePackage) + .and(TemplateParameter.EXCHANGE_ROLE, ExchangeRole.CONSUMER) + .and(TemplateParameter.LOCAL_TYPE_NAME, codeElementFormatter.simpleNameOf(qualifiedSchemaName)) + .andResolve(TemplateParameter.EXCHANGE_ADAPTER_NAME, CsharpTemplateStandard.EXCHANGE_ADAPTER::resolveClassname) + .andResolve(TemplateParameter.EXCHANGE_MAPPER_NAME, CsharpTemplateStandard.EXCHANGE_MAPPER::resolveClassname) + .addImport(qualifiedSchemaName); + } + + @Override + public TemplateParameters parameters() { + return parameters; + } + + @Override + public TemplateStandard standard() { + return CsharpTemplateStandard.EXCHANGE_ADAPTER; + } + +} diff --git a/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeBootstrapTemplateData.java b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeBootstrapTemplateData.java new file mode 100644 index 00000000..21a0174d --- /dev/null +++ b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeBootstrapTemplateData.java @@ -0,0 +1,66 @@ +// Copyright © 2012-2022 VLINGO LABS. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +package io.vlingo.xoom.designer.codegen.csharp.exchange; + +import io.vlingo.xoom.codegen.template.TemplateData; +import io.vlingo.xoom.codegen.template.TemplateParameters; +import io.vlingo.xoom.codegen.template.TemplateStandard; +import io.vlingo.xoom.designer.codegen.csharp.CsharpTemplateStandard; +import io.vlingo.xoom.designer.codegen.java.TemplateParameter; +import io.vlingo.xoom.lattice.model.IdentifiedDomainEvent; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public class ExchangeBootstrapTemplateData extends TemplateData { + + private final TemplateParameters parameters; + + public static TemplateData from(final String exchangePackage, + final List exchanges) { + return new ExchangeBootstrapTemplateData(exchangePackage, exchanges); + } + + public ExchangeBootstrapTemplateData(final String exchangePackage, + final List exchanges) { + parameters = TemplateParameters.with(TemplateParameter.PACKAGE_NAME, exchangePackage) + .and(TemplateParameter.EXCHANGES, exchanges) + .and(TemplateParameter.EXCHANGE_BOOTSTRAP_NAME, CsharpTemplateStandard.EXCHANGE_BOOTSTRAP.resolveClassname()) + .and(TemplateParameter.PRODUCER_EXCHANGES, joinProducerExchangeNames(exchanges)) + .addImports(resolveImports(exchanges)); + } + + private String joinProducerExchangeNames(final List exchanges) { + return exchanges.stream().filter(Exchange::isProducer).map(exchange -> Formatter.formatExchangeVariableName(exchange.name)) + .distinct().collect(Collectors.joining(", ")); + } + + private Set resolveImports(final List exchanges) { + final Set imports = exchanges.stream().filter(Exchange::isConsumer) + .flatMap(exchange -> exchange.coveys.stream()) + .map(covey -> covey.qualifiedLocalClassName) + .collect(Collectors.toSet()); + + if (exchanges.stream().anyMatch(Exchange::isProducer)) { + imports.add(IdentifiedDomainEvent.class.getCanonicalName()); + } + + return imports; + } + + @Override + public TemplateParameters parameters() { + return parameters; + } + + @Override + public TemplateStandard standard() { + return CsharpTemplateStandard.EXCHANGE_BOOTSTRAP; + } +} diff --git a/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeDispatcherTemplateData.java b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeDispatcherTemplateData.java new file mode 100644 index 00000000..06e524f6 --- /dev/null +++ b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeDispatcherTemplateData.java @@ -0,0 +1,70 @@ +// Copyright © 2012-2022 VLINGO LABS. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +package io.vlingo.xoom.designer.codegen.csharp.exchange; + +import io.vlingo.xoom.codegen.content.Content; +import io.vlingo.xoom.codegen.content.ContentQuery; +import io.vlingo.xoom.codegen.parameter.CodeGenerationParameter; +import io.vlingo.xoom.codegen.template.TemplateData; +import io.vlingo.xoom.codegen.template.TemplateParameters; +import io.vlingo.xoom.codegen.template.TemplateStandard; +import io.vlingo.xoom.designer.codegen.Label; +import io.vlingo.xoom.designer.codegen.csharp.CsharpTemplateStandard; +import io.vlingo.xoom.designer.codegen.csharp.TemplateParameter; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.toList; + +public class ExchangeDispatcherTemplateData extends TemplateData { + + private final TemplateParameters parameters; + + public static TemplateData from(final String exchangePackage, + final Stream aggregates, + final List contents) { + final List producerExchanges = aggregates + .flatMap(aggregate -> aggregate.retrieveAllRelated(Label.EXCHANGE)) + .filter(aggregate -> aggregate.retrieveRelatedValue(Label.ROLE, ExchangeRole::of).isProducer()) + .collect(toList()); + + return new ExchangeDispatcherTemplateData(exchangePackage, producerExchanges, contents); + } + + private ExchangeDispatcherTemplateData(final String exchangePackage, final List producerExchanges, + final List contents) { + this.parameters = TemplateParameters.with(TemplateParameter.PACKAGE_NAME, exchangePackage) + .and(TemplateParameter.PRODUCER_EXCHANGES, ProducerExchange.from(producerExchanges)) + .addImports(resolveImports(producerExchanges, contents)); + } + + private Set resolveImports(final List producerExchanges, final List contents) { + return producerExchanges.stream().flatMap(exchange -> exchange.retrieveAllRelated(Label.DOMAIN_EVENT)) + .map(event -> ContentQuery.findFullyQualifiedClassName(CsharpTemplateStandard.DOMAIN_EVENT, event.value, contents)) + .collect(Collectors.toSet()); + } + + @Override + public TemplateParameters parameters() { + return parameters; + } + + @Override + public TemplateStandard standard() { + return CsharpTemplateStandard.EXCHANGE_DISPATCHER; + } + + @Override + @SuppressWarnings("rawtypes") + public boolean isPlaceholder() { + return parameters.find(TemplateParameter.PRODUCER_EXCHANGES).isEmpty(); + } +} diff --git a/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeGenerationStep.java b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeGenerationStep.java new file mode 100644 index 00000000..82ce2b76 --- /dev/null +++ b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeGenerationStep.java @@ -0,0 +1,57 @@ +// Copyright © 2012-2022 VLINGO LABS. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +package io.vlingo.xoom.designer.codegen.csharp.exchange; + +import io.vlingo.xoom.codegen.CodeGenerationContext; +import io.vlingo.xoom.codegen.dialect.Dialect; +import io.vlingo.xoom.codegen.parameter.CodeGenerationParameter; +import io.vlingo.xoom.codegen.template.TemplateData; +import io.vlingo.xoom.codegen.template.TemplateProcessingStep; +import io.vlingo.xoom.designer.codegen.Label; + +import java.util.List; +import java.util.stream.Collectors; + +public class ExchangeGenerationStep extends TemplateProcessingStep { + + @Override + protected List buildTemplatesData(final CodeGenerationContext context) { + final List aggregates = + context.parametersOf(Label.AGGREGATE).filter(aggregate -> aggregate.hasAny(Label.EXCHANGE)) + .collect(Collectors.toList()); + final List valueObjects = + context.parametersOf(Label.VALUE_OBJECT) + .collect(Collectors.toList()); + + return ExchangeTemplateDataFactory.build(resolvePackage(context), aggregates, valueObjects, context.contents()); + } + + private String resolvePackage(final CodeGenerationContext context) { + return String.format("%s.%s.%s", context.parameterOf(Label.PACKAGE), "Infrastructure", "Exchange"); + } + + @Override + public boolean shouldProcess(final CodeGenerationContext context) { + if (!context.hasParameter(Label.AGGREGATE)) { + return false; + } + return resolveDialect(context).equals(Dialect.C_SHARP) && + context.parametersOf(Label.AGGREGATE).anyMatch(aggregate -> aggregate.hasAny(Label.EXCHANGE)); + } + + @Override + protected Dialect resolveDialect(CodeGenerationContext context) { + final String dialectName = dialectNameFrom(context); + return dialectName.isEmpty() ? super.resolveDialect(context) : Dialect.withName(dialectName); + } + + private String dialectNameFrom(CodeGenerationContext context) { + return context.parameterOf(Label.DIALECT); + } + +} diff --git a/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeMapperTemplateData.java b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeMapperTemplateData.java new file mode 100644 index 00000000..16e29bb7 --- /dev/null +++ b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeMapperTemplateData.java @@ -0,0 +1,94 @@ +// Copyright © 2012-2022 VLINGO LABS. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +package io.vlingo.xoom.designer.codegen.csharp.exchange; + +import io.vlingo.xoom.codegen.content.CodeElementFormatter; +import io.vlingo.xoom.codegen.parameter.CodeGenerationParameter; +import io.vlingo.xoom.codegen.template.TemplateData; +import io.vlingo.xoom.codegen.template.TemplateParameters; +import io.vlingo.xoom.codegen.template.TemplateStandard; +import io.vlingo.xoom.designer.codegen.Label; +import io.vlingo.xoom.designer.codegen.csharp.CsharpTemplateStandard; +import io.vlingo.xoom.designer.codegen.csharp.ExchangeDetail; +import io.vlingo.xoom.designer.codegen.csharp.TemplateParameter; +import io.vlingo.xoom.turbo.ComponentRegistry; + +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class ExchangeMapperTemplateData extends TemplateData { + + private final boolean placeholder; + private final TemplateParameters parameters; + + public static List from(final String exchangePackage, final Stream aggregates) { + final CodeElementFormatter codeElementFormatter = ComponentRegistry.withName("cSharpCodeFormatter"); + final List collectedAggregates = aggregates.collect(Collectors.toList()); + final List mappers = forConsumerExchanges(codeElementFormatter, exchangePackage, collectedAggregates); + mappers.add(forProducerExchanges(exchangePackage, collectedAggregates)); + return mappers; + } + + private static TemplateData forProducerExchanges(final String exchangePackage, final List aggregates) { + final Predicate producerPresent = + exchange -> exchange.retrieveRelatedValue(Label.ROLE, ExchangeRole::of).isProducer(); + + final boolean hasProducerExchange = aggregates.stream() + .flatMap(aggregate -> aggregate.retrieveAllRelated(Label.EXCHANGE)) + .anyMatch(producerPresent); + + return new ExchangeMapperTemplateData(exchangePackage, !hasProducerExchange); + } + + private static List forConsumerExchanges(final CodeElementFormatter codeElementFormatter, + final String exchangePackage, + final List aggregates) { + return aggregates.stream().flatMap(aggregate -> aggregate.retrieveAllRelated(Label.EXCHANGE)) + .flatMap(ExchangeDetail::findConsumedQualifiedEventNames) + .map(schemaQualifiedName -> new ExchangeMapperTemplateData(codeElementFormatter, exchangePackage, schemaQualifiedName)) + .collect(Collectors.toList()); + } + + private ExchangeMapperTemplateData(final CodeElementFormatter codeElementFormatter, final String exchangePackage, + final String schemaQualifiedName) { + this.parameters = + TemplateParameters.with(TemplateParameter.PACKAGE_NAME, exchangePackage).and(TemplateParameter.EXCHANGE_ROLE, ExchangeRole.CONSUMER) + .and(TemplateParameter.LOCAL_TYPE_NAME, codeElementFormatter.simpleNameOf(schemaQualifiedName)) + .andResolve(TemplateParameter.EXCHANGE_MAPPER_NAME, param -> standard().resolveClassname(param)) + .addImport(schemaQualifiedName); + + this.placeholder = false; + } + + private ExchangeMapperTemplateData(final String exchangePackage, final Boolean placeholder) { + this.parameters = TemplateParameters.with(TemplateParameter.PACKAGE_NAME, exchangePackage) + .and(TemplateParameter.EXCHANGE_ROLE, ExchangeRole.PRODUCER) + .andResolve(TemplateParameter.EXCHANGE_MAPPER_NAME, param -> standard().resolveClassname(param)); + + this.placeholder = placeholder; + } + + + @Override + public TemplateParameters parameters() { + return parameters; + } + + @Override + public TemplateStandard standard() { + return CsharpTemplateStandard.EXCHANGE_MAPPER; + } + + @Override + public boolean isPlaceholder() { + return placeholder; + } + +} diff --git a/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangePropertiesTemplateData.java b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangePropertiesTemplateData.java new file mode 100644 index 00000000..ed2b78a5 --- /dev/null +++ b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangePropertiesTemplateData.java @@ -0,0 +1,42 @@ +// Copyright © 2012-2022 VLINGO LABS. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +package io.vlingo.xoom.designer.codegen.csharp.exchange; + +import io.vlingo.xoom.codegen.template.TemplateData; +import io.vlingo.xoom.codegen.template.TemplateParameters; +import io.vlingo.xoom.codegen.template.TemplateStandard; +import io.vlingo.xoom.designer.codegen.csharp.CsharpTemplateStandard; +import io.vlingo.xoom.designer.codegen.csharp.TemplateParameter; + +import java.util.List; +import java.util.stream.Collectors; + +public class ExchangePropertiesTemplateData extends TemplateData { + + private final TemplateParameters parameters; + + public static TemplateData from(final List exchanges) { + return new ExchangePropertiesTemplateData(exchanges); + } + + private ExchangePropertiesTemplateData(final List exchanges) { + this.parameters = TemplateParameters.with(TemplateParameter.EXCHANGES, exchanges) + .and(TemplateParameter.INLINE_EXCHANGE_NAMES, exchanges.stream().map(exchange -> exchange.name).collect(Collectors.joining(";"))); + } + + @Override + public TemplateParameters parameters() { + return parameters; + } + + @Override + public TemplateStandard standard() { + return CsharpTemplateStandard.TURBO_SETTINGS; + } + +} diff --git a/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeReceiver.java b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeReceiver.java new file mode 100644 index 00000000..531214c8 --- /dev/null +++ b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeReceiver.java @@ -0,0 +1,83 @@ +// Copyright © 2012-2022 VLINGO LABS. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +package io.vlingo.xoom.designer.codegen.csharp.exchange; + +import io.vlingo.xoom.codegen.content.CodeElementFormatter; +import io.vlingo.xoom.codegen.parameter.CodeGenerationParameter; +import io.vlingo.xoom.designer.codegen.Label; +import io.vlingo.xoom.designer.codegen.csharp.AggregateDetail; +import io.vlingo.xoom.designer.codegen.csharp.CsharpTemplateStandard; +import io.vlingo.xoom.designer.codegen.csharp.MethodScope; +import io.vlingo.xoom.designer.codegen.csharp.ValueObjectDetail; +import io.vlingo.xoom.designer.codegen.csharp.formatting.AggregateMethodInvocation; +import io.vlingo.xoom.turbo.ComponentRegistry; + +import java.util.List; +import java.util.stream.Collectors; + +public class ExchangeReceiver { + + public final String modelActor; + public final String modelProtocol; + public final String modelMethod; + public final String modelVariable; + public final String localTypeName; + public final String modelMethodParameters; + public final String innerClassName; + public final String localTypeQualifiedName; + public final boolean dispatchToFactoryMethod; + + public static List from(final CodeGenerationParameter exchange, List valueObjects) { + return exchange.retrieveAllRelated(Label.RECEIVER) + .map(receiver -> new ExchangeReceiver(exchange, receiver, valueObjects)) + .collect(Collectors.toList()); + } + + private ExchangeReceiver(final CodeGenerationParameter exchange, + final CodeGenerationParameter receiver, List valueObjects) { + final CodeElementFormatter codeElementFormatter = ComponentRegistry.withName("cSharpCodeFormatter"); + + final CodeGenerationParameter aggregateMethod = + AggregateDetail.methodWithName(exchange.parent(), receiver.retrieveRelatedValue(Label.MODEL_METHOD)); + + final Schema schema = receiver.retrieveOneRelated(Label.SCHEMA).object(); + + this.modelMethod = aggregateMethod.value; + this.localTypeName = schema.simpleClassName(); + this.modelProtocol = exchange.parent(Label.AGGREGATE).value; + this.innerClassName = schema.innerReceiverClassName(); + this.modelActor = CsharpTemplateStandard.AGGREGATE.resolveClassname(modelProtocol); + this.modelVariable = codeElementFormatter.simpleNameToAttribute(modelProtocol); + this.modelMethodParameters = resolveModelMethodParameters(aggregateMethod, valueObjects); + this.dispatchToFactoryMethod = aggregateMethod.retrieveRelatedValue(Label.FACTORY_METHOD, Boolean::valueOf); + this.localTypeQualifiedName = schema.qualifiedName(); + } + + private String resolveModelMethodParameters(final CodeGenerationParameter method, List valueObjects) { + final boolean factoryMethod = method.retrieveRelatedValue(Label.FACTORY_METHOD, Boolean::valueOf); + final MethodScope methodScope = factoryMethod ? MethodScope.STATIC : MethodScope.INSTANCE; + + CodeGenerationParameter stateField = AggregateDetail.stateFieldWithName(method.parent(), method.retrieveRelatedValue(Label.METHOD_PARAMETER)); + if(isNullObjectOrNotValueObject(stateField, valueObjects)) + return AggregateMethodInvocation.accessingParametersFromConsumedEvent("stage").format(method, methodScope); + + CodeGenerationParameter valueObject = ValueObjectDetail.valueObjectOf(stateField.retrieveRelatedValue(Label.FIELD_TYPE), valueObjects.stream()); + return AggregateMethodInvocation.accessingValueObjectParametersFromConsumedEvent("stage", valueObject).format(method, methodScope); + } + + private boolean isNullObjectOrNotValueObject(final CodeGenerationParameter stateField, + final List valueObjects) { + if(stateField == null) + return true; + + return valueObjects.stream() + .noneMatch(vo -> vo.value.equals((stateField.retrieveRelatedValue(Label.FIELD_TYPE)))) && !ValueObjectDetail.isValueObject(stateField); + } + + +} diff --git a/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeReceiverHolderTemplateData.java b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeReceiverHolderTemplateData.java new file mode 100644 index 00000000..1571ebcb --- /dev/null +++ b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeReceiverHolderTemplateData.java @@ -0,0 +1,82 @@ +// Copyright © 2012-2022 VLINGO LABS. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +package io.vlingo.xoom.designer.codegen.csharp.exchange; + +import io.vlingo.xoom.actors.Definition; +import io.vlingo.xoom.codegen.content.Content; +import io.vlingo.xoom.codegen.content.ContentQuery; +import io.vlingo.xoom.codegen.parameter.CodeGenerationParameter; +import io.vlingo.xoom.codegen.template.TemplateData; +import io.vlingo.xoom.codegen.template.TemplateParameters; +import io.vlingo.xoom.codegen.template.TemplateStandard; +import io.vlingo.xoom.designer.codegen.Label; +import io.vlingo.xoom.designer.codegen.csharp.AggregateDetail; +import io.vlingo.xoom.designer.codegen.csharp.CsharpTemplateStandard; +import io.vlingo.xoom.designer.codegen.csharp.TemplateParameter; +import io.vlingo.xoom.designer.codegen.csharp.ValueObjectDetail; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class ExchangeReceiverHolderTemplateData extends TemplateData { + + private final TemplateParameters parameters; + + public static List from(final String exchangePackage, final Stream aggregates, + List valueObjects, final List contents) { + return aggregates.flatMap(aggregate -> aggregate.retrieveAllRelated(Label.EXCHANGE)) + .filter(exchange -> exchange.retrieveRelatedValue(Label.ROLE, ExchangeRole::of).isConsumer()) + .map(exchange -> new ExchangeReceiverHolderTemplateData(exchangePackage, exchange, valueObjects, contents)) + .collect(Collectors.toList()); + } + + private ExchangeReceiverHolderTemplateData(final String exchangePackage, final CodeGenerationParameter exchange, + List valueObjects, final List contents) { + final List receiversParameters = ExchangeReceiver.from(exchange, valueObjects); + this.parameters = + TemplateParameters.with(TemplateParameter.PACKAGE_NAME, exchangePackage) + .and(TemplateParameter.EXCHANGE_RECEIVERS, receiversParameters) + .and(TemplateParameter.AGGREGATE_PROTOCOL_NAME, exchange.parent().value) + .andResolve(TemplateParameter.EXCHANGE_RECEIVER_HOLDER_NAME, params -> standard().resolveClassname(params)) + .addImports(resolveImports(exchange, receiversParameters, contents, valueObjects)); + } + + private Set resolveImports(final CodeGenerationParameter exchange, final List receiversParameters, + final List contents, List valueObjects) { + final Set imports = new HashSet<>(); + final String aggregateName = exchange.parent().value; + final boolean involvesActorLoad = + receiversParameters.stream().anyMatch(receiver -> !receiver.dispatchToFactoryMethod); + + if (involvesActorLoad) { + final String aggregateEntityName = CsharpTemplateStandard.AGGREGATE.resolveClassname(aggregateName); + imports.add(ContentQuery.findFullyQualifiedClassName(CsharpTemplateStandard.AGGREGATE, aggregateEntityName, contents)); + imports.add(Definition.class.getCanonicalName()); + } + + imports.add(ContentQuery.findFullyQualifiedClassName(CsharpTemplateStandard.AGGREGATE_PROTOCOL, AggregateDetail.resolveProtocolNameFor(exchange.parent()), contents)); + imports.addAll(receiversParameters.stream().map(receiver -> receiver.localTypeQualifiedName).collect(Collectors.toSet())); + imports.addAll(receiversParameters.stream().map(receiver -> receiver.localTypeQualifiedName).collect(Collectors.toSet())); + imports.addAll(valueObjects.stream().map(vo -> ValueObjectDetail.resolveTypeImport(vo.value, contents)).collect(Collectors.toSet())); + return imports; + } + + @Override + public TemplateParameters parameters() { + return parameters; + } + + @Override + public TemplateStandard standard() { + return CsharpTemplateStandard.EXCHANGE_RECEIVER_HOLDER; + } + +} diff --git a/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeRole.java b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeRole.java new file mode 100644 index 00000000..1e663bf2 --- /dev/null +++ b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeRole.java @@ -0,0 +1,32 @@ +// Copyright © 2012-2022 VLINGO LABS. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +package io.vlingo.xoom.designer.codegen.csharp.exchange; + +import freemarker.template.utility.StringUtil; + +public enum ExchangeRole { + + CONSUMER, + PRODUCER; + + public static ExchangeRole of(final String roleName) { + return valueOf(roleName.toUpperCase()); + } + + public boolean isConsumer() { + return equals(CONSUMER); + } + + public boolean isProducer() { + return equals(PRODUCER); + } + + public String formatName() { + return StringUtil.capitalize(name().toLowerCase()); + } +} diff --git a/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeTemplateDataFactory.java b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeTemplateDataFactory.java new file mode 100644 index 00000000..82ed3e6b --- /dev/null +++ b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeTemplateDataFactory.java @@ -0,0 +1,52 @@ +// Copyright © 2012-2022 VLINGO LABS. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +package io.vlingo.xoom.designer.codegen.csharp.exchange; + +import io.vlingo.xoom.codegen.content.Content; +import io.vlingo.xoom.codegen.parameter.CodeGenerationParameter; +import io.vlingo.xoom.codegen.template.TemplateData; +import io.vlingo.xoom.designer.codegen.Label; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.toList; + +public class ExchangeTemplateDataFactory { + + public static List build(final String exchangePackage, final List aggregates, + final List valueObjects, final List contents) { + final Supplier> filteredAggregates = () -> + aggregates.stream().filter(aggregate -> aggregate.hasAny(Label.EXCHANGE)); + + final List exchanges = + Exchange.from(filteredAggregates.get().collect(toList())); + + final List mappers = + ExchangeMapperTemplateData.from(exchangePackage, filteredAggregates.get()); + + final List holders = + ExchangeReceiverHolderTemplateData.from(exchangePackage, filteredAggregates.get(), valueObjects, contents); + + final List adapters = + ExchangeAdapterTemplateData.from(exchangePackage, filteredAggregates.get()); + + final List properties = + Arrays.asList(ExchangePropertiesTemplateData.from(exchanges)); + + final List dispatcher = + Arrays.asList(ExchangeDispatcherTemplateData.from(exchangePackage, filteredAggregates.get(), contents)); + + final List bootstrap = + Arrays.asList(ExchangeBootstrapTemplateData.from(exchangePackage, exchanges)); + + return Stream.of(mappers, holders, adapters, properties, dispatcher, bootstrap).flatMap(List::stream).collect(toList()); + } +} diff --git a/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/Formatter.java b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/Formatter.java new file mode 100644 index 00000000..fdc5017a --- /dev/null +++ b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/Formatter.java @@ -0,0 +1,36 @@ +// Copyright © 2012-2022 VLINGO LABS. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +package io.vlingo.xoom.designer.codegen.csharp.exchange; + +import io.vlingo.xoom.codegen.parameter.CodeGenerationParameter; + +public class Formatter { + + public static String formatExchangeVariableName(final String exchangeName) { + boolean shouldUpper = false; + final StringBuilder formatted = new StringBuilder(); + for (char character : exchangeName.toLowerCase().toCharArray()) { + if (!Character.isJavaIdentifierPart(character)) { + shouldUpper = true; + continue; + } + formatted.append(shouldUpper ? String.valueOf(character).toUpperCase() : character); + shouldUpper = false; + } + return formatted.toString(); + } + + public static String formatSchemaTypeName(final CodeGenerationParameter schema) { + return schema.value.split(":")[3]; + } + + public static String formatReceiverInnerClassName(final CodeGenerationParameter schema) { + return formatSchemaTypeName(schema) + "Receiver"; + } + +} diff --git a/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ProducerExchange.java b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ProducerExchange.java new file mode 100644 index 00000000..4881f919 --- /dev/null +++ b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ProducerExchange.java @@ -0,0 +1,43 @@ +// Copyright © 2012-2022 VLINGO LABS. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +package io.vlingo.xoom.designer.codegen.csharp.exchange; + +import io.vlingo.xoom.codegen.parameter.CodeGenerationParameter; +import io.vlingo.xoom.designer.codegen.Label; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public class ProducerExchange { + + private final String name; + private final Set events; + + public static List from(final List exchanges) { + return exchanges.stream().map(exchange -> exchange.value).distinct() + .map(name -> new ProducerExchange(name, exchanges)) + .collect(Collectors.toList()); + } + + private ProducerExchange(final String exchangeName, + final List allExchangeParameters) { + this.name = exchangeName; + this.events = allExchangeParameters.stream().filter(exchange -> exchange.value.equals(exchangeName)) + .flatMap(exchange -> exchange.retrieveAllRelated(Label.DOMAIN_EVENT)) + .map(event -> event.value).collect(Collectors.toSet()); + } + + public String getName() { + return name; + } + + public Set getEvents() { + return events; + } +} diff --git a/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/Schema.java b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/Schema.java new file mode 100644 index 00000000..0823c718 --- /dev/null +++ b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/exchange/Schema.java @@ -0,0 +1,73 @@ +// Copyright © 2012-2022 VLINGO LABS. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +package io.vlingo.xoom.designer.codegen.csharp.exchange; + +import io.vlingo.xoom.codegen.content.CodeElementFormatter; +import io.vlingo.xoom.codegen.parameter.CodeGenerationParameter; +import io.vlingo.xoom.designer.codegen.CodeGenerationProperties; +import io.vlingo.xoom.designer.codegen.Label; +import io.vlingo.xoom.designer.codegen.csharp.FieldDetail; +import io.vlingo.xoom.turbo.ComponentRegistry; + +import java.util.function.Function; + +import static io.vlingo.xoom.designer.codegen.csharp.FieldDetail.isDateTime; + +public class Schema { + + public final String file; + public final String reference; + private static final Function schemaFieldTypeConverter = t -> isDateTime(t) ? "timestamp" : t; + + public Schema(final String schemaReference) { + this.reference = schemaReference; + this.file = null; + } + + public Schema(final String schemaGroup, final CodeGenerationParameter publishedLanguage) { + if (!(publishedLanguage.isLabeled(Label.DOMAIN_EVENT) || publishedLanguage.isLabeled(Label.VALUE_OBJECT))) { + throw new IllegalArgumentException("A Domain Event or Value Object parameter is expected."); + } + this.reference = String.format("%s:%s:%s", schemaGroup, publishedLanguage.value, CodeGenerationProperties.DEFAULT_SCHEMA_VERSION); + this.file = publishedLanguage.value + ".vss"; + } + + public String simpleClassName() { + return reference.split(":")[3]; + } + + public String qualifiedName() { + final CodeElementFormatter formatter = ComponentRegistry.withName("defaultCodeFormatter"); + final String packageName = reference.split(":")[2] + ".event"; + return formatter.qualifiedNameOf(packageName, simpleClassName()); + } + + public String innerReceiverClassName() { + return simpleClassName() + "Receiver"; + } + + static String resolveFieldDeclaration(final CodeGenerationParameter field) { + final String fieldType = field.retrieveRelatedValue(Label.FIELD_TYPE); + + if (FieldDetail.isAssignableToValueObject(field)) { + return String.format("data.%s:%s %s", fieldType, CodeGenerationProperties.DEFAULT_SCHEMA_VERSION, field.value); + } + + if (FieldDetail.isCollection(field)) { + final String convertedGenericType = schemaFieldTypeConverter.apply(FieldDetail.genericTypeOf(field.parent(), field.value)); + if (FieldDetail.isScalar(convertedGenericType)) { + return String.format("%s[] %s", convertedGenericType.toLowerCase(), field.value); + } else { + return String.format("data.%s:%s[] %s", schemaFieldTypeConverter.apply(convertedGenericType), CodeGenerationProperties.DEFAULT_SCHEMA_VERSION, field.value); + } + } + + return schemaFieldTypeConverter.apply(fieldType).toLowerCase() + " " + field.value; + } + +} diff --git a/src/main/java/io/vlingo/xoom/designer/codegen/csharp/formatting/AggregateMethodInvocation.java b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/formatting/AggregateMethodInvocation.java index 0d686dbe..8ff889f6 100644 --- a/src/main/java/io/vlingo/xoom/designer/codegen/csharp/formatting/AggregateMethodInvocation.java +++ b/src/main/java/io/vlingo/xoom/designer/codegen/csharp/formatting/AggregateMethodInvocation.java @@ -24,12 +24,19 @@ public class AggregateMethodInvocation implements Formatters.Arguments { private final String stageVariableName; private final ParametersOwner parametersOwner; + private CodeGenerationParameter valueObject; private static final String FIELD_ACCESS_PATTERN = "%s.%s"; public static AggregateMethodInvocation accessingParametersFromDataObject(final String stageVariableName) { return new AggregateMethodInvocation(stageVariableName, AggregateMethodInvocation.ParametersOwner.DATA_OBJECT); } + public AggregateMethodInvocation(String stageVariableName, AggregateMethodInvocation.ParametersOwner consumedEvent, + CodeGenerationParameter valueObject) { + this(stageVariableName, consumedEvent); + this.valueObject = valueObject; + } + public AggregateMethodInvocation(final String stageVariableName) { this(stageVariableName, ParametersOwner.NONE); } @@ -47,6 +54,14 @@ public String format(final CodeGenerationParameter method, final MethodScope sco .flatMap(Collection::stream) .collect(Collectors.joining(", ")); } + public static AggregateMethodInvocation accessingParametersFromConsumedEvent(final String stageVariableName) { + return new AggregateMethodInvocation(stageVariableName, AggregateMethodInvocation.ParametersOwner.CONSUMED_EVENT); + } + + public static AggregateMethodInvocation accessingValueObjectParametersFromConsumedEvent(final String stageVariableName, + final CodeGenerationParameter valueObject) { + return new AggregateMethodInvocation(stageVariableName, AggregateMethodInvocation.ParametersOwner.CONSUMED_EVENT, valueObject); + } private List formatMethodParameters(final CodeGenerationParameter method) { return method.retrieveAllRelated(Label.METHOD_PARAMETER).map(this::resolveFieldAccess).collect(toList()); diff --git a/src/main/resources/codegen/c_sharp/ConsumerExchangeAdapter.ftl b/src/main/resources/codegen/c_sharp/ConsumerExchangeAdapter.ftl new file mode 100644 index 00000000..6447473e --- /dev/null +++ b/src/main/resources/codegen/c_sharp/ConsumerExchangeAdapter.ftl @@ -0,0 +1,41 @@ +<#list imports as import> +using ${import.qualifiedClassName}; + + +namespace ${packageName}; + +/** + * See ExchangeAdapter + */ +public class ${exchangeAdapterName} : IExchangeAdapter<${localTypeName}, string, Message> +{ + + private string _supportedSchemaName; + + public ${exchangeAdapterName}(string supportedSchemaName) + { + _supportedSchemaName = supportedSchemaName; + } + + public ${localTypeName} FromExchange(Message exchangeMessage) + { + return new ${exchangeMapperName}().ExternalToLocal(exchangeMessage.PayloadAsText()); + } + + public Message ToExchange(${localTypeName} local) + { + var messagePayload = new ${exchangeMapperName}().LocalToExternal(local); + return new Message(messagePayload, MessageParameters.Bare().DeliveryMode(DeliveryMode.Durable)); + } + + public bool supports(Object exchangeMessage) + { + if(typeof(exchangeMessage) != typeof(Message)) + { + return false; + } + var schemaName = ((Message) exchangeMessage).MessageParameters.TypeName(); + return _supportedSchemaName.EqualsIgnoreCase(schemaName); + } + +} diff --git a/src/main/resources/codegen/c_sharp/ConsumerExchangeMapper.ftl b/src/main/resources/codegen/c_sharp/ConsumerExchangeMapper.ftl new file mode 100644 index 00000000..62e2b282 --- /dev/null +++ b/src/main/resources/codegen/c_sharp/ConsumerExchangeMapper.ftl @@ -0,0 +1,23 @@ +<#list imports as import> +using ${import.qualifiedClassName}; + +using Vlingo.Xoom.Common.Serialization; + +namespace ${packageName}; + +/** + * See ExchangeMapper + */ +public class ${exchangeMapperName} : IExchangeMapper<${localTypeName}, string> +{ + + public string LocalToExternal(${localTypeName} local) + { + return JsonSerialization.Serialized(local); + } + + public ${localTypeName} ExternalToLocal(string external) + { + return JsonSerialization.Deserialized<${localTypeName}>(external); + } +} diff --git a/src/main/resources/codegen/c_sharp/ExchangeBootstrap.ftl b/src/main/resources/codegen/c_sharp/ExchangeBootstrap.ftl new file mode 100644 index 00000000..ebc5b0c8 --- /dev/null +++ b/src/main/resources/codegen/c_sharp/ExchangeBootstrap.ftl @@ -0,0 +1,53 @@ +<#if producerExchanges?has_content> +using Vlingo.Xoom.Symbio.Store.Dispatch; + + +<#list imports as import> +using ${import.qualifiedClassName}; + + +namespace ${packageName}; + +public class ${exchangeBootstrapName} : ExchangeInitializer +{ + + <#if producerExchanges?has_content> + private IDispatcher _dispatcher; + + + public void Init(Grid stage) + { + var exchangeSettings = ExchangeSettings.LoadOne(Settings.Properties); + + <#list exchanges as exchange> + var ${exchange.settingsName} = exchangeSettings.MapToConnection(); + + var ${exchange.variableName} = ExchangeFactory.FanOutInstanceQuietly(${exchange.settingsName}, exchangeSettings.ExchangeName, true); + + try + { + <#list exchange.coveys as covey> + ${exchange.variableName}.register(Covey.Of( + new MessageSender(${exchange.variableName}.connection()), + ${covey.receiverInstantiation}, + ${covey.adapterInstantiation}, + typeof(${covey.localClass}), + typeof(${covey.externalClass}), + typeof(Message))); + + + } catch (InactiveBrokerExchangeException exception) + { + Stage.World.DefaultLogger.Error("Unable to register covey(s) for exchange"); + Stage.World.DefaultLogger.Warn(exception.Message); + } + + + <#if producerExchanges?has_content>_dispatcher = new ExchangeDispatcher(${producerExchanges}); + } + + <#if producerExchanges?has_content> + public IDispatcher Dispatcher => _dispatcher; + + +} \ No newline at end of file diff --git a/src/main/resources/codegen/c_sharp/ExchangeDispatcher.ftl b/src/main/resources/codegen/c_sharp/ExchangeDispatcher.ftl new file mode 100644 index 00000000..55cf7ebd --- /dev/null +++ b/src/main/resources/codegen/c_sharp/ExchangeDispatcher.ftl @@ -0,0 +1,64 @@ + +<#if imports?has_content> +<#list imports as import> +using ${import.qualifiedClassName}; + + + +namespace ${packageName}; + +/** + * See + * + * Dispatcher and ProjectionDispatcher + * + */ +public class ExchangeDispatcher : IDispatcher, IState>>, IConfirmDispatchedResultInterest +{ + private DispatcherControl _control; + private Exchange _producer; + private List _outgoingEvents = new ArrayList<>(); + + public ExchangeDispatcher(Exchange producer) + { + this.producer = producer; + <#list producerExchanges as exchange> + <#list exchange.events as event> + _outgoingEvents.Add(nameof(${event})); + + + } + + public void Dispatch(Dispatchable, State> dispatchable) + { + foreach (var entry in dispatchable.Entries()) + { + Send(JsonSerialization.Deserialized(entry.EntryData(), entry.Typed())); + } + + _control.ConfirmDispatched(dispatchable.Id, this); + } + + public void ConfirmDispatchedResultedIn(Result result, string dispatchId) + { + } + + public void ControlWith(DispatcherControl control) + { + this.control = control; + } + + private void Send(Object event) + { + if(ShouldPublish(event)) + { + _producer.Send(event); + } + } + + private bool ShouldPublish(Object event) + { + return _outgoingEvents.Contains(nameof(event)); + } + +} diff --git a/src/main/resources/codegen/c_sharp/ExchangeReceiverHolder.ftl b/src/main/resources/codegen/c_sharp/ExchangeReceiverHolder.ftl new file mode 100644 index 00000000..028f36d2 --- /dev/null +++ b/src/main/resources/codegen/c_sharp/ExchangeReceiverHolder.ftl @@ -0,0 +1,35 @@ +<#list imports as import> +using ${import.qualifiedClassName}; + + +namespace ${packageName}; + +public class ${exchangeReceiverHolderName} +{ + +<#list exchangeReceivers as receiver> + /** + * See ExchangeReceiver + */ + static class ${receiver.innerClassName} : IExchangeReceiver<${receiver.localTypeName}> { + + private Grid _stage; + + public ${receiver.innerClassName}(Grid stage) + { + _stage = stage; + } + + public void Receive(${receiver.localTypeName} event) + { + <#if receiver.dispatchToFactoryMethod> + ${receiver.modelProtocol}.${receiver.modelMethod}(${receiver.modelMethodParameters}); + <#else> + _stage.ActorOf<${receiver.modelProtocol}>(_stage.AddressFactory().From(event.Id), Definition.Has(typeof(${receiver.modelActor}), Definition.Parameters(event.Id))) + .AndFinallyConsume(${receiver.modelVariable} => ${receiver.modelVariable}.${receiver.modelMethod}(${receiver.modelMethodParameters})); + + } + } + + +} diff --git a/src/main/resources/codegen/c_sharp/ProducerExchangeAdapter.ftl b/src/main/resources/codegen/c_sharp/ProducerExchangeAdapter.ftl new file mode 100644 index 00000000..8e96097c --- /dev/null +++ b/src/main/resources/codegen/c_sharp/ProducerExchangeAdapter.ftl @@ -0,0 +1,40 @@ +<#list imports as import> +using ${import.qualifiedClassName}; + + +namespace ${packageName}; + +/** + * See ExchangeAdapter + */ +public class ${exchangeAdapterName} : IExchangeAdapter +{ + + private static String SCHEMA_PREFIX = "${schemaGroupName}"; + + private ${exchangeMapperName} _mapper = new ${exchangeMapperName}(); + + public IdentifiedDomainEvent FromExchange(Message exchangeMessage) + { + return _mapper.ExternalToLocal(exchangeMessage); + } + + public Message ToExchange(IdentifiedDomainEvent event) + { + var message = _mapper.LocalToExternal(event); + message.MessageParameters.TypeName(ResolveFullSchemaReference(event)); + return message; + } + + public bool Supports(Object exchangeMessage) + { + return false; + } + + private string ResolveFullSchemaReference(IdentifiedDomainEvent event) + { + var semanticVersion = SemanticVersion.ToString(event.SourceTypeVersion); + return $"{SCHEMA_PREFIX}{typeof(event)}{semanticVersion}"; + } + +} diff --git a/src/main/resources/codegen/c_sharp/ProducerExchangeMapper.ftl b/src/main/resources/codegen/c_sharp/ProducerExchangeMapper.ftl new file mode 100644 index 00000000..5807e181 --- /dev/null +++ b/src/main/resources/codegen/c_sharp/ProducerExchangeMapper.ftl @@ -0,0 +1,32 @@ +using Vlingo.Xoom.Common.Serialization; + +namespace ${packageName}; + +/** + * See ExchangeMapper + */ +public class ${exchangeMapperName} : IExchangeMapper +{ + + public Message LocalToExternal(IdentifiedDomainEvent event) + { + var messagePayload = JsonSerialization.Serialized(event); + return new Message(messagePayload, MessageParameters.Bare().DeliveryMode(MessageParameters.DeliveryMode.Durable)); + } + + public IdentifiedDomainEvent ExternalToLocal(Message message) + { + try + { + var eventFullyQualifiedName = message.messageParameters.typeName(); + + var eventClass = eventFullyQualifiedName.GetType(); + + return JsonSerialization.Deserialized(message.PayloadAsText()); + } catch (TypeNotFoundException e) + { + throw new IllegalArgumentException("Unable to handle message containing " + + message.MessageParameters.TypeName(), e); + } + } +} diff --git a/src/test/java/io/vlingo/xoom/designer/codegen/csharp/exchange/CodeGenerationParametersBuilder.java b/src/test/java/io/vlingo/xoom/designer/codegen/csharp/exchange/CodeGenerationParametersBuilder.java new file mode 100644 index 00000000..d31f1f46 --- /dev/null +++ b/src/test/java/io/vlingo/xoom/designer/codegen/csharp/exchange/CodeGenerationParametersBuilder.java @@ -0,0 +1,155 @@ +// Copyright © 2012-2022 VLINGO LABS. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. +package io.vlingo.xoom.designer.codegen.csharp.exchange; + +import io.vlingo.xoom.codegen.dialect.Dialect; +import io.vlingo.xoom.codegen.parameter.CodeGenerationParameter; +import io.vlingo.xoom.designer.codegen.DeploymentType; +import io.vlingo.xoom.designer.codegen.Label; +import io.vlingo.xoom.designer.codegen.csharp.DeploymentSettings; + +import java.util.stream.Stream; + +public class CodeGenerationParametersBuilder { + + public static Stream threeExchanges() { + final CodeGenerationParameter dialect = + CodeGenerationParameter.of(Label.DIALECT, Dialect.C_SHARP); + + final CodeGenerationParameter idField = + CodeGenerationParameter.of(Label.STATE_FIELD, "id") + .relate(Label.FIELD_TYPE, "String"); + + final CodeGenerationParameter nameField = + CodeGenerationParameter.of(Label.STATE_FIELD, "name") + .relate(Label.FIELD_TYPE, "Name"); + + final CodeGenerationParameter rankField = + CodeGenerationParameter.of(Label.STATE_FIELD, "rank") + .relate(Label.FIELD_TYPE, "Rank"); + + final CodeGenerationParameter createdOn = + CodeGenerationParameter.of(Label.STATE_FIELD, "createdOn") + .relate(Label.FIELD_TYPE, "LocalDateTime"); + + final CodeGenerationParameter factoryMethod = + CodeGenerationParameter.of(Label.AGGREGATE_METHOD, "withName") + .relate(Label.METHOD_PARAMETER, "name") + .relate(Label.FACTORY_METHOD, "true"); + + final CodeGenerationParameter rankMethod = + CodeGenerationParameter.of(Label.AGGREGATE_METHOD, "changeRank") + .relate(Label.METHOD_PARAMETER, "rank"); + + final CodeGenerationParameter blockMethod = + CodeGenerationParameter.of(Label.AGGREGATE_METHOD, "block") + .relate(Label.METHOD_PARAMETER, "name"); + + final CodeGenerationParameter otherAppExchange = + CodeGenerationParameter.of(Label.EXCHANGE, "book-store-exchange") + .relate(Label.ROLE, ExchangeRole.CONSUMER) + .relate(CodeGenerationParameter.of(Label.RECEIVER) + .relate(CodeGenerationParameter.ofObject(Label.SCHEMA, new Schema("vlingo:xoom:io.vlingo.xoom.otherapp:OtherAggregateDefined:0.0.1"))) + .relate(Label.MODEL_METHOD, "withName")) + .relate(CodeGenerationParameter.of(Label.RECEIVER) + .relate(CodeGenerationParameter.ofObject(Label.SCHEMA, new Schema("vlingo:xoom:io.vlingo.xoom.otherapp:OtherAggregateUpdated:0.0.2"))) + .relate(Label.MODEL_METHOD, "changeRank")) + .relate(CodeGenerationParameter.of(Label.RECEIVER) + .relate(CodeGenerationParameter.ofObject(Label.SCHEMA, new Schema("vlingo:xoom:io.vlingo.xoom.otherapp:OtherAggregateRemoved:0.0.3"))) + .relate(Label.MODEL_METHOD, "block")); + + final CodeGenerationParameter authorExchange = + CodeGenerationParameter.of(Label.EXCHANGE, "book-store-exchange") + .relate(Label.ROLE, ExchangeRole.PRODUCER) + .relate(Label.SCHEMA_GROUP, "vlingo:xoom:io.vlingo.xoomapp") + .relate(Label.DOMAIN_EVENT, "AuthorBlocked") + .relate(Label.DOMAIN_EVENT, "AuthorRated"); + + final CodeGenerationParameter authorRatedEvent = + CodeGenerationParameter.of(Label.DOMAIN_EVENT, "AuthorRated") + .relate(idField).relate(rankField).relate(createdOn); + + final CodeGenerationParameter authorBlockedEvent = + CodeGenerationParameter.of(Label.DOMAIN_EVENT, "AuthorBlocked") + .relate(nameField); + + final CodeGenerationParameter authorAggregate = + CodeGenerationParameter.of(Label.AGGREGATE, "Author") + .relate(otherAppExchange).relate(authorExchange) + .relate(idField).relate(nameField).relate(rankField) + .relate(createdOn).relate(factoryMethod).relate(rankMethod) + .relate(blockMethod).relate(authorRatedEvent).relate(authorBlockedEvent); + + final CodeGenerationParameter titleField = + CodeGenerationParameter.of(Label.STATE_FIELD, "name") + .relate(Label.FIELD_TYPE, "Name"); + + final CodeGenerationParameter priceField = + CodeGenerationParameter.of(Label.STATE_FIELD, "rank") + .relate(Label.FIELD_TYPE, "int"); + + final CodeGenerationParameter deploymentSettings = + CodeGenerationParameter.ofObject(Label.DEPLOYMENT_SETTINGS, + DeploymentSettings.with(DeploymentType.NONE, "", "", "", 8988)); + + final CodeGenerationParameter bookExchange = + CodeGenerationParameter.of(Label.EXCHANGE, "book-store-exchange") + .relate(Label.ROLE, ExchangeRole.PRODUCER) + .relate(Label.SCHEMA_GROUP, "vlingo:xoom:io.vlingo.xoomapp") + .relate(Label.DOMAIN_EVENT, "BookSoldOut") + .relate(Label.DOMAIN_EVENT, "BookPurchased"); + + final CodeGenerationParameter bookSoldOutEvent = + CodeGenerationParameter.of(Label.DOMAIN_EVENT, "BookSoldOut") + .relate(titleField); + + final CodeGenerationParameter bookPurchasedEvent = + CodeGenerationParameter.of(Label.DOMAIN_EVENT, "BookPurchased") + .relate(priceField); + + final CodeGenerationParameter bookAggregate = + CodeGenerationParameter.of(Label.AGGREGATE, "Book") + .relate(titleField).relate(priceField) + .relate(bookSoldOutEvent).relate(bookPurchasedEvent) + .relate(bookExchange); + + final CodeGenerationParameter nameValueObject = + CodeGenerationParameter.of(Label.VALUE_OBJECT, "Name") + .relate(CodeGenerationParameter.of(Label.VALUE_OBJECT_FIELD, "firstName") + .relate(Label.FIELD_TYPE, "String")) + .relate(CodeGenerationParameter.of(Label.VALUE_OBJECT_FIELD, "lastName") + .relate(Label.FIELD_TYPE, "String")); + + final CodeGenerationParameter rankValueObject = + CodeGenerationParameter.of(Label.VALUE_OBJECT, "Rank") + .relate(CodeGenerationParameter.of(Label.VALUE_OBJECT_FIELD, "points") + .relate(Label.FIELD_TYPE, "int")) + .relate(CodeGenerationParameter.of(Label.VALUE_OBJECT_FIELD, "classification") + .relate(Label.FIELD_TYPE, "Classification")); + + final CodeGenerationParameter classificationValueObject = + CodeGenerationParameter.of(Label.VALUE_OBJECT, "Classification") + .relate(CodeGenerationParameter.of(Label.VALUE_OBJECT_FIELD, "labels") + .relate(Label.FIELD_TYPE, "String") + .relate(Label.COLLECTION_TYPE, "Set")) + .relate(CodeGenerationParameter.of(Label.VALUE_OBJECT_FIELD, "classifiers") + .relate(Label.FIELD_TYPE, "Classifier") + .relate(Label.COLLECTION_TYPE, "List")) + .relate(CodeGenerationParameter.of(Label.VALUE_OBJECT_FIELD, "classifiedOn") + .relate(Label.FIELD_TYPE, "LocalDate")); + + final CodeGenerationParameter classifierValueObject = + CodeGenerationParameter.of(Label.VALUE_OBJECT, "Classifier") + .relate(CodeGenerationParameter.of(Label.VALUE_OBJECT_FIELD, "name") + .relate(Label.FIELD_TYPE, "String")); + + return Stream.of(dialect, deploymentSettings, authorAggregate, bookAggregate, + nameValueObject, rankValueObject, classificationValueObject, classifierValueObject); + } + + +} diff --git a/src/test/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ContentBuilder.java b/src/test/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ContentBuilder.java new file mode 100644 index 00000000..1f14d787 --- /dev/null +++ b/src/test/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ContentBuilder.java @@ -0,0 +1,140 @@ +// Copyright © 2012-2022 VLINGO LABS. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. +package io.vlingo.xoom.designer.codegen.csharp.exchange; + +import io.vlingo.xoom.codegen.content.Content; +import io.vlingo.xoom.codegen.template.OutputFile; +import io.vlingo.xoom.designer.codegen.csharp.CsharpTemplateStandard; + +import java.util.Arrays; +import java.util.List; + +public class ContentBuilder { + + public static Content authorContent() { + return Content.with(CsharpTemplateStandard.AGGREGATE_PROTOCOL, new OutputFile("", "IAuthor.cs"), null, null, AUTHOR_CONTENT_TEXT); + } + + public static Content authorEntityContent() { + return Content.with(CsharpTemplateStandard.AGGREGATE, new OutputFile("", "AuthorEntity.cs"), null, null, AUTHOR_ENTITY_CONTENT_TEXT); + } + + public static Content bookContent() { + return Content.with(CsharpTemplateStandard.AGGREGATE_PROTOCOL, new OutputFile("", "IBook.cs"), null, null, BOOK_CONTENT_TEXT); + } + + public static Content authorDataObjectContent() { + return Content.with(CsharpTemplateStandard.DATA_OBJECT, new OutputFile("", "AuthorData.cs"), null, null, AUTHOR_DATA_CONTENT_TEXT); + } + + public static Content authorRatedEvent() { + return Content.with(CsharpTemplateStandard.DOMAIN_EVENT, new OutputFile("", "AuthorRated.cs"), null, null, AUTHOR_RATED_CONTENT_TEXT); + } + + public static Content authorBlockedEvent() { + return Content.with(CsharpTemplateStandard.DOMAIN_EVENT, new OutputFile("", "AuthorBlocked.cs"), null, null, AUTHOR_BLOCKED_CONTENT_TEXT); + } + + public static Content bookSoldOutEvent() { + return Content.with(CsharpTemplateStandard.DOMAIN_EVENT, new OutputFile("", "BookSoldOut.cs"), null, null, BOOK_SOLD_OUT_CONTENT_TEXT); + } + + public static Content bookPurchasedEvent() { + return Content.with(CsharpTemplateStandard.DOMAIN_EVENT, new OutputFile("", "BookPurchased.cs"), null, null, BOOK_PURCHASED_CONTENT_TEXT); + } + + public static Content rankValueObject() { + return Content.with(CsharpTemplateStandard.VALUE_OBJECT, new OutputFile("", "Rank.cs"), null, null, RANK_VALUE_OBJECT_CONTENT_TEXT); + } + + public static Content nameValueObject() { + return Content.with(CsharpTemplateStandard.VALUE_OBJECT, new OutputFile("", "Name.cs"), null, null, NAME_VALUE_OBJECT_CONTENT_TEXT); + } + + public static Content classificationValueObject() { + return Content.with(CsharpTemplateStandard.VALUE_OBJECT, new OutputFile("", "Classification.cs"), null, null, CLASSIFICATION_VALUE_OBJECT_CONTENT_TEXT); + } + + public static Content classifierValueObject() { + return Content.with(CsharpTemplateStandard.VALUE_OBJECT, new OutputFile("", "Classifier.cs"), null, null, CLASSIFIER_VALUE_OBJECT_CONTENT_TEXT); + } + + public static List contents() { + return Arrays.asList(authorContent(), authorEntityContent(), bookContent(), authorDataObjectContent(), + authorRatedEvent(), authorBlockedEvent(), bookSoldOutEvent(), bookPurchasedEvent(), + rankValueObject(), nameValueObject(), classificationValueObject(), classifierValueObject()); + } + + private static final String AUTHOR_CONTENT_TEXT = + "namespace Io.Vlingo.Xoomapp.Model.Author; \\n" + + "public interface IAuthor { \\n" + + "... \\n" + + "}"; + + private static final String BOOK_CONTENT_TEXT = + "namespace Io.Vlingo.Xoomapp.Model.Book; \\n" + + "public interface Book { \\n" + + "... \\n" + + "}"; + + private static final String AUTHOR_ENTITY_CONTENT_TEXT = + "namespace Io.Vlingo.Xoomapp.Model.Author; \\n" + + "public class AuthorEntity { \\n" + + "... \\n" + + "}"; + + private static final String AUTHOR_DATA_CONTENT_TEXT = + "namespace Io.Vlingo.Xoomapp.Infrastructure; \\n" + + "public class AuthorData { \\n" + + "... \\n" + + "}"; + + private static final String AUTHOR_RATED_CONTENT_TEXT = + "namespace Io.Vlingo.Xoomapp.Model.Author; \\n" + + "public class AuthorRated extends DomainEvent { \\n" + + "... \\n" + + "}"; + + private static final String AUTHOR_BLOCKED_CONTENT_TEXT = + "namespace Io.Vlingo.Xoomapp.Model.Author; \\n" + + "public class AuthorRated extends DomainEvent { \\n" + + "... \\n" + + "}"; + + private static final String BOOK_SOLD_OUT_CONTENT_TEXT = + "namespace Io.Vlingo.Xoomapp.Model.Book; \\n" + + "public class BookSoldOut extends DomainEvent { \\n" + + "... \\n" + + "}"; + + private static final String BOOK_PURCHASED_CONTENT_TEXT = + "namespace Io.Vlingo.Xoomapp.Model.Book; \\n" + + "public class BookPurchased extends DomainEvent { \\n" + + "... \\n" + + "}"; + + private static final String NAME_VALUE_OBJECT_CONTENT_TEXT = + "namespace Io.Vlingo.Xoomapp.Model; \\n" + + "public class Name { \\n" + + "... \\n" + + "}"; + private static final String CLASSIFICATION_VALUE_OBJECT_CONTENT_TEXT = + "namespace Io.Vlingo.Xoomapp.Model; \\n" + + "public class Classification { \\n" + + "... \\n" + + "}"; + private static final String CLASSIFIER_VALUE_OBJECT_CONTENT_TEXT = + "namespace Io.Vlingo.Xoomapp.Model; \\n" + + "public class Classifier { \\n" + + "... \\n" + + "}"; + private static final String RANK_VALUE_OBJECT_CONTENT_TEXT = + "namespace Io.Vlingo.Xoomapp.Model; \\n" + + "public class Rank { \\n" + + "... \\n" + + "}"; +} diff --git a/src/test/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeGenerationStepTest.java b/src/test/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeGenerationStepTest.java new file mode 100644 index 00000000..b66ea548 --- /dev/null +++ b/src/test/java/io/vlingo/xoom/designer/codegen/csharp/exchange/ExchangeGenerationStepTest.java @@ -0,0 +1,94 @@ +// Copyright © 2012-2022 VLINGO LABS. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +package io.vlingo.xoom.designer.codegen.csharp.exchange; + +import io.vlingo.xoom.codegen.CodeGenerationContext; +import io.vlingo.xoom.codegen.TextExpectation; +import io.vlingo.xoom.codegen.content.Content; +import io.vlingo.xoom.codegen.content.TextBasedContent; +import io.vlingo.xoom.codegen.parameter.CodeGenerationParameters; +import io.vlingo.xoom.designer.codegen.CodeGenerationTest; +import io.vlingo.xoom.designer.codegen.Label; +import io.vlingo.xoom.designer.codegen.csharp.CsharpTemplateStandard; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static java.util.stream.Collectors.toList; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ExchangeGenerationStepTest extends CodeGenerationTest { + + @Test + public void testThatExchangeCodeIsGenerated() { + final CodeGenerationParameters parameters = CodeGenerationParameters.empty() + .addAll(CodeGenerationParametersBuilder.threeExchanges().collect(toList())); + + final CodeGenerationContext context = CodeGenerationContext.with(parameters) + .with(Label.PACKAGE, "Io.Vlingo.Xoomapp"); + + context.contents(ContentBuilder.contents().toArray(new Content[]{})); + + new ExchangeGenerationStep().process(context); + + Assertions.assertEquals(25, context.contents().size()); + + final Content exchangeBootstrap = + context.findContent(CsharpTemplateStandard.EXCHANGE_BOOTSTRAP, "ExchangeBootstrap"); + + final Content authorExchangeReceivers = + context.findContent(CsharpTemplateStandard.EXCHANGE_RECEIVER_HOLDER, "AuthorExchangeReceivers"); + + final Content otherAggregateDefinedAdapter = + context.findContent(CsharpTemplateStandard.EXCHANGE_ADAPTER, "OtherAggregateDefinedAdapter"); + + final Content otherAggregateUpdatedAdapter = + context.findContent(CsharpTemplateStandard.EXCHANGE_ADAPTER, "OtherAggregateUpdatedAdapter"); + + final Content otherAggregateRemovedAdapter = + context.findContent(CsharpTemplateStandard.EXCHANGE_ADAPTER, "OtherAggregateRemovedAdapter"); + + final Content authorProducerAdapter = + context.findContent(CsharpTemplateStandard.EXCHANGE_ADAPTER, "AuthorProducerAdapter"); + + final Content otherAggregateDefinedMapper = + context.findContent(CsharpTemplateStandard.EXCHANGE_MAPPER, "OtherAggregateDefinedMapper"); + + final Content otherAggregateUpdatedMapper = + context.findContent(CsharpTemplateStandard.EXCHANGE_MAPPER, "OtherAggregateUpdatedMapper"); + + final Content otherAggregateRemovedMapper = + context.findContent(CsharpTemplateStandard.EXCHANGE_MAPPER, "OtherAggregateRemovedMapper"); + + final Content bookProducerAdapter = + context.findContent(CsharpTemplateStandard.EXCHANGE_ADAPTER, "BookProducerAdapter"); + + final Content domainEventMapper = + context.findContent(CsharpTemplateStandard.EXCHANGE_MAPPER, "DomainEventMapper"); + + final Content exchangeDispatcher = + context.findContent(CsharpTemplateStandard.EXCHANGE_DISPATCHER, "ExchangeDispatcher"); + + final Content exchangeProperties = + context.findContent(CsharpTemplateStandard.TURBO_SETTINGS, "xoom-turbo"); + + assertEquals(((TextBasedContent) authorExchangeReceivers).text, (TextExpectation.onCSharp().read("author-exchange-receivers"))); + assertEquals(((TextBasedContent) exchangeBootstrap).text, (TextExpectation.onCSharp().read("exchange-bootstrap"))); + assertEquals(((TextBasedContent) authorExchangeReceivers).text, (TextExpectation.onCSharp().read("author-exchange-receivers"))); + assertEquals(((TextBasedContent) otherAggregateDefinedAdapter).text, (TextExpectation.onCSharp().read("other-aggregate-defined-adapter"))); + assertEquals(((TextBasedContent) otherAggregateUpdatedAdapter).text, (TextExpectation.onCSharp().read("other-aggregate-updated-adapter"))); + assertEquals(((TextBasedContent) otherAggregateRemovedAdapter).text, (TextExpectation.onCSharp().read("other-aggregate-removed-adapter"))); + assertEquals(((TextBasedContent) authorProducerAdapter).text, (TextExpectation.onCSharp().read("author-producer-adapter"))); + assertEquals(((TextBasedContent) otherAggregateDefinedMapper).text, (TextExpectation.onCSharp().read("other-aggregate-defined-mapper"))); + assertEquals(((TextBasedContent) otherAggregateUpdatedMapper).text, (TextExpectation.onCSharp().read("other-aggregate-updated-mapper"))); + assertEquals(((TextBasedContent) otherAggregateRemovedMapper).text, (TextExpectation.onCSharp().read("other-aggregate-removed-mapper"))); + assertEquals(((TextBasedContent) bookProducerAdapter).text, (TextExpectation.onCSharp().read("book-producer-adapter"))); + assertEquals(((TextBasedContent) domainEventMapper).text, (TextExpectation.onCSharp().read("domain-event-mapper"))); + assertEquals(((TextBasedContent) exchangeDispatcher).text, (TextExpectation.onCSharp().read("exchange-dispatcher"))); + assertEquals(((TextBasedContent) exchangeProperties).text, ((TextExpectation.onCSharp().read("xoom-turbo")))); + } +} diff --git a/src/test/resources/text-expectations/c_sharp/author-exchange-receivers.text b/src/test/resources/text-expectations/c_sharp/author-exchange-receivers.text new file mode 100644 index 00000000..4eb00dd2 --- /dev/null +++ b/src/test/resources/text-expectations/c_sharp/author-exchange-receivers.text @@ -0,0 +1,73 @@ +using io.vlingo.xoom.actors.Definition; +using Io.Vlingo.Xoomapp.Model.Classification; +using Io.Vlingo.Xoomapp.Model.Rank; +using io.vlingo.xoom.otherapp.event.OtherAggregateRemoved; +using io.vlingo.xoom.otherapp.event.OtherAggregateUpdated; +using io.vlingo.xoom.otherapp.event.OtherAggregateDefined; +using Io.Vlingo.Xoomapp.Model.Name; +using Io.Vlingo.Xoomapp.Model.Author.AuthorEntity; +using Io.Vlingo.Xoomapp.Model.Author.IAuthor; +using Io.Vlingo.Xoomapp.Model.Classifier; + +namespace Io.Vlingo.Xoomapp.Infrastructure.Exchange; + +public class AuthorExchangeReceivers +{ + + /** + * See ExchangeReceiver + */ + static class OtherAggregateDefinedReceiver : IExchangeReceiver { + + private Grid _stage; + + public OtherAggregateDefinedReceiver(Grid stage) + { + _stage = stage; + } + + public void Receive(OtherAggregateDefined event) + { + Author.withName(stage, event.Name); + } + } + + /** + * See ExchangeReceiver + */ + static class OtherAggregateUpdatedReceiver : IExchangeReceiver { + + private Grid _stage; + + public OtherAggregateUpdatedReceiver(Grid stage) + { + _stage = stage; + } + + public void Receive(OtherAggregateUpdated event) + { + _stage.ActorOf(_stage.AddressFactory().From(event.Id), Definition.Has(typeof(AuthorEntity), Definition.Parameters(event.Id))) + .AndFinallyConsume(author => author.changeRank(event.Rank)); + } + } + + /** + * See ExchangeReceiver + */ + static class OtherAggregateRemovedReceiver : IExchangeReceiver { + + private Grid _stage; + + public OtherAggregateRemovedReceiver(Grid stage) + { + _stage = stage; + } + + public void Receive(OtherAggregateRemoved event) + { + _stage.ActorOf(_stage.AddressFactory().From(event.Id), Definition.Has(typeof(AuthorEntity), Definition.Parameters(event.Id))) + .AndFinallyConsume(author => author.block(event.Name)); + } + } + +} diff --git a/src/test/resources/text-expectations/c_sharp/author-producer-adapter.text b/src/test/resources/text-expectations/c_sharp/author-producer-adapter.text new file mode 100644 index 00000000..be678cfc --- /dev/null +++ b/src/test/resources/text-expectations/c_sharp/author-producer-adapter.text @@ -0,0 +1,38 @@ +using io.vlingo.xoom.lattice.model.IdentifiedDomainEvent; + +namespace Io.Vlingo.Xoomapp.Infrastructure.Exchange; + +/** + * See ExchangeAdapter + */ +public class AuthorProducerAdapter : IExchangeAdapter +{ + + private static String SCHEMA_PREFIX = "vlingo:xoom:io.vlingo.xoomapp"; + + private DomainEventMapper _mapper = new DomainEventMapper(); + + public IdentifiedDomainEvent FromExchange(Message exchangeMessage) + { + return _mapper.ExternalToLocal(exchangeMessage); + } + + public Message ToExchange(IdentifiedDomainEvent event) + { + var message = _mapper.LocalToExternal(event); + message.MessageParameters.TypeName(ResolveFullSchemaReference(event)); + return message; + } + + public bool Supports(Object exchangeMessage) + { + return false; + } + + private string ResolveFullSchemaReference(IdentifiedDomainEvent event) + { + var semanticVersion = SemanticVersion.ToString(event.SourceTypeVersion); + return $"{SCHEMA_PREFIX}{typeof(event)}{semanticVersion}"; + } + +} diff --git a/src/test/resources/text-expectations/c_sharp/book-producer-adapter.text b/src/test/resources/text-expectations/c_sharp/book-producer-adapter.text new file mode 100644 index 00000000..61a29064 --- /dev/null +++ b/src/test/resources/text-expectations/c_sharp/book-producer-adapter.text @@ -0,0 +1,38 @@ +using io.vlingo.xoom.lattice.model.IdentifiedDomainEvent; + +namespace Io.Vlingo.Xoomapp.Infrastructure.Exchange; + +/** + * See ExchangeAdapter + */ +public class BookProducerAdapter : IExchangeAdapter +{ + + private static String SCHEMA_PREFIX = "vlingo:xoom:io.vlingo.xoomapp"; + + private DomainEventMapper _mapper = new DomainEventMapper(); + + public IdentifiedDomainEvent FromExchange(Message exchangeMessage) + { + return _mapper.ExternalToLocal(exchangeMessage); + } + + public Message ToExchange(IdentifiedDomainEvent event) + { + var message = _mapper.LocalToExternal(event); + message.MessageParameters.TypeName(ResolveFullSchemaReference(event)); + return message; + } + + public bool Supports(Object exchangeMessage) + { + return false; + } + + private string ResolveFullSchemaReference(IdentifiedDomainEvent event) + { + var semanticVersion = SemanticVersion.ToString(event.SourceTypeVersion); + return $"{SCHEMA_PREFIX}{typeof(event)}{semanticVersion}"; + } + +} diff --git a/src/test/resources/text-expectations/c_sharp/domain-event-mapper.text b/src/test/resources/text-expectations/c_sharp/domain-event-mapper.text new file mode 100644 index 00000000..815e299d --- /dev/null +++ b/src/test/resources/text-expectations/c_sharp/domain-event-mapper.text @@ -0,0 +1,32 @@ +using Vlingo.Xoom.Common.Serialization; + +namespace Io.Vlingo.Xoomapp.Infrastructure.Exchange; + +/** + * See ExchangeMapper + */ +public class DomainEventMapper : IExchangeMapper +{ + + public Message LocalToExternal(IdentifiedDomainEvent event) + { + var messagePayload = JsonSerialization.Serialized(event); + return new Message(messagePayload, MessageParameters.Bare().DeliveryMode(MessageParameters.DeliveryMode.Durable)); + } + + public IdentifiedDomainEvent ExternalToLocal(Message message) + { + try + { + var eventFullyQualifiedName = message.messageParameters.typeName(); + + var eventClass = eventFullyQualifiedName.GetType(); + + return JsonSerialization.Deserialized(message.PayloadAsText()); + } catch (TypeNotFoundException e) + { + throw new IllegalArgumentException("Unable to handle message containing " + + message.MessageParameters.TypeName(), e); + } + } +} diff --git a/src/test/resources/text-expectations/c_sharp/exchange-bootstrap.text b/src/test/resources/text-expectations/c_sharp/exchange-bootstrap.text new file mode 100644 index 00000000..c3ea53fe --- /dev/null +++ b/src/test/resources/text-expectations/c_sharp/exchange-bootstrap.text @@ -0,0 +1,76 @@ +using Vlingo.Xoom.Symbio.Store.Dispatch; + +using io.vlingo.xoom.lattice.model.IdentifiedDomainEvent; +using io.vlingo.xoom.otherapp.event.OtherAggregateRemoved; +using io.vlingo.xoom.otherapp.event.OtherAggregateUpdated; +using io.vlingo.xoom.otherapp.event.OtherAggregateDefined; + +namespace Io.Vlingo.Xoomapp.Infrastructure.Exchange; + +public class ExchangeBootstrap : ExchangeInitializer +{ + + private IDispatcher _dispatcher; + + public void Init(Grid stage) + { + var exchangeSettings = ExchangeSettings.LoadOne(Settings.Properties); + + var bookStoreExchangeSettings = exchangeSettings.MapToConnection(); + + var bookStoreExchange = ExchangeFactory.FanOutInstanceQuietly(bookStoreExchangeSettings, exchangeSettings.ExchangeName, true); + + try + { + bookStoreExchange.register(Covey.Of( + new MessageSender(bookStoreExchange.connection()), + new AuthorExchangeReceivers.OtherAggregateDefinedReceiver(stage), + new OtherAggregateDefinedAdapter("vlingo:xoom:io.vlingo.xoom.otherapp:OtherAggregateDefined:0.0.1"), + typeof(OtherAggregateDefined), + typeof(String), + typeof(Message))); + + bookStoreExchange.register(Covey.Of( + new MessageSender(bookStoreExchange.connection()), + received => {}, + new AuthorProducerAdapter(), + typeof(IdentifiedDomainEvent), + typeof(IdentifiedDomainEvent), + typeof(Message))); + + bookStoreExchange.register(Covey.Of( + new MessageSender(bookStoreExchange.connection()), + received => {}, + new BookProducerAdapter(), + typeof(IdentifiedDomainEvent), + typeof(IdentifiedDomainEvent), + typeof(Message))); + + bookStoreExchange.register(Covey.Of( + new MessageSender(bookStoreExchange.connection()), + new AuthorExchangeReceivers.OtherAggregateUpdatedReceiver(stage), + new OtherAggregateUpdatedAdapter("vlingo:xoom:io.vlingo.xoom.otherapp:OtherAggregateUpdated:0.0.2"), + typeof(OtherAggregateUpdated), + typeof(String), + typeof(Message))); + + bookStoreExchange.register(Covey.Of( + new MessageSender(bookStoreExchange.connection()), + new AuthorExchangeReceivers.OtherAggregateRemovedReceiver(stage), + new OtherAggregateRemovedAdapter("vlingo:xoom:io.vlingo.xoom.otherapp:OtherAggregateRemoved:0.0.3"), + typeof(OtherAggregateRemoved), + typeof(String), + typeof(Message))); + + } catch (InactiveBrokerExchangeException exception) + { + Stage.World.DefaultLogger.Error("Unable to register covey(s) for exchange"); + Stage.World.DefaultLogger.Warn(exception.Message); + } + + _dispatcher = new ExchangeDispatcher(bookStoreExchange); + } + + public IDispatcher Dispatcher => _dispatcher; + +} \ No newline at end of file diff --git a/src/test/resources/text-expectations/c_sharp/exchange-dispatcher.text b/src/test/resources/text-expectations/c_sharp/exchange-dispatcher.text new file mode 100644 index 00000000..10b282e7 --- /dev/null +++ b/src/test/resources/text-expectations/c_sharp/exchange-dispatcher.text @@ -0,0 +1,62 @@ + +using Io.Vlingo.Xoomapp.Model.Book.BookSoldOut; +using Io.Vlingo.Xoomapp.Model.Book.BookPurchased; +using Io.Vlingo.Xoomapp.Model.Author.AuthorRated; +using Io.Vlingo.Xoomapp.Model.Author.AuthorBlocked; + +namespace Io.Vlingo.Xoomapp.Infrastructure.Exchange; + +/** + * See + * + * Dispatcher and ProjectionDispatcher + * + */ +public class ExchangeDispatcher : IDispatcher, IState>>, IConfirmDispatchedResultInterest +{ + private DispatcherControl _control; + private Exchange _producer; + private List _outgoingEvents = new ArrayList<>(); + + public ExchangeDispatcher(Exchange producer) + { + this.producer = producer; + _outgoingEvents.Add(nameof(AuthorRated)); + _outgoingEvents.Add(nameof(BookSoldOut)); + _outgoingEvents.Add(nameof(BookPurchased)); + _outgoingEvents.Add(nameof(AuthorBlocked)); + } + + public void Dispatch(Dispatchable, State> dispatchable) + { + foreach (var entry in dispatchable.Entries()) + { + Send(JsonSerialization.Deserialized(entry.EntryData(), entry.Typed())); + } + + _control.ConfirmDispatched(dispatchable.Id, this); + } + + public void ConfirmDispatchedResultedIn(Result result, string dispatchId) + { + } + + public void ControlWith(DispatcherControl control) + { + this.control = control; + } + + private void Send(Object event) + { + if(ShouldPublish(event)) + { + _producer.Send(event); + } + } + + private bool ShouldPublish(Object event) + { + return _outgoingEvents.Contains(nameof(event)); + } + +} diff --git a/src/test/resources/text-expectations/c_sharp/other-aggregate-defined-adapter.text b/src/test/resources/text-expectations/c_sharp/other-aggregate-defined-adapter.text new file mode 100644 index 00000000..e79af7c3 --- /dev/null +++ b/src/test/resources/text-expectations/c_sharp/other-aggregate-defined-adapter.text @@ -0,0 +1,39 @@ +using io.vlingo.xoom.otherapp.event.OtherAggregateDefined; + +namespace Io.Vlingo.Xoomapp.Infrastructure.Exchange; + +/** + * See ExchangeAdapter + */ +public class OtherAggregateDefinedAdapter : IExchangeAdapter +{ + + private string _supportedSchemaName; + + public OtherAggregateDefinedAdapter(string supportedSchemaName) + { + _supportedSchemaName = supportedSchemaName; + } + + public OtherAggregateDefined FromExchange(Message exchangeMessage) + { + return new OtherAggregateDefinedMapper().ExternalToLocal(exchangeMessage.PayloadAsText()); + } + + public Message ToExchange(OtherAggregateDefined local) + { + var messagePayload = new OtherAggregateDefinedMapper().LocalToExternal(local); + return new Message(messagePayload, MessageParameters.Bare().DeliveryMode(DeliveryMode.Durable)); + } + + public bool supports(Object exchangeMessage) + { + if(typeof(exchangeMessage) != typeof(Message)) + { + return false; + } + var schemaName = ((Message) exchangeMessage).MessageParameters.TypeName(); + return _supportedSchemaName.EqualsIgnoreCase(schemaName); + } + +} diff --git a/src/test/resources/text-expectations/c_sharp/other-aggregate-defined-mapper.text b/src/test/resources/text-expectations/c_sharp/other-aggregate-defined-mapper.text new file mode 100644 index 00000000..a93f2411 --- /dev/null +++ b/src/test/resources/text-expectations/c_sharp/other-aggregate-defined-mapper.text @@ -0,0 +1,21 @@ +using io.vlingo.xoom.otherapp.event.OtherAggregateDefined; +using Vlingo.Xoom.Common.Serialization; + +namespace Io.Vlingo.Xoomapp.Infrastructure.Exchange; + +/** + * See ExchangeMapper + */ +public class OtherAggregateDefinedMapper : IExchangeMapper +{ + + public string LocalToExternal(OtherAggregateDefined local) + { + return JsonSerialization.Serialized(local); + } + + public OtherAggregateDefined ExternalToLocal(string external) + { + return JsonSerialization.Deserialized(external); + } +} diff --git a/src/test/resources/text-expectations/c_sharp/other-aggregate-removed-adapter.text b/src/test/resources/text-expectations/c_sharp/other-aggregate-removed-adapter.text new file mode 100644 index 00000000..be110b39 --- /dev/null +++ b/src/test/resources/text-expectations/c_sharp/other-aggregate-removed-adapter.text @@ -0,0 +1,39 @@ +using io.vlingo.xoom.otherapp.event.OtherAggregateRemoved; + +namespace Io.Vlingo.Xoomapp.Infrastructure.Exchange; + +/** + * See ExchangeAdapter + */ +public class OtherAggregateRemovedAdapter : IExchangeAdapter +{ + + private string _supportedSchemaName; + + public OtherAggregateRemovedAdapter(string supportedSchemaName) + { + _supportedSchemaName = supportedSchemaName; + } + + public OtherAggregateRemoved FromExchange(Message exchangeMessage) + { + return new OtherAggregateRemovedMapper().ExternalToLocal(exchangeMessage.PayloadAsText()); + } + + public Message ToExchange(OtherAggregateRemoved local) + { + var messagePayload = new OtherAggregateRemovedMapper().LocalToExternal(local); + return new Message(messagePayload, MessageParameters.Bare().DeliveryMode(DeliveryMode.Durable)); + } + + public bool supports(Object exchangeMessage) + { + if(typeof(exchangeMessage) != typeof(Message)) + { + return false; + } + var schemaName = ((Message) exchangeMessage).MessageParameters.TypeName(); + return _supportedSchemaName.EqualsIgnoreCase(schemaName); + } + +} diff --git a/src/test/resources/text-expectations/c_sharp/other-aggregate-removed-mapper.text b/src/test/resources/text-expectations/c_sharp/other-aggregate-removed-mapper.text new file mode 100644 index 00000000..cc44eadd --- /dev/null +++ b/src/test/resources/text-expectations/c_sharp/other-aggregate-removed-mapper.text @@ -0,0 +1,21 @@ +using io.vlingo.xoom.otherapp.event.OtherAggregateRemoved; +using Vlingo.Xoom.Common.Serialization; + +namespace Io.Vlingo.Xoomapp.Infrastructure.Exchange; + +/** + * See ExchangeMapper + */ +public class OtherAggregateRemovedMapper : IExchangeMapper +{ + + public string LocalToExternal(OtherAggregateRemoved local) + { + return JsonSerialization.Serialized(local); + } + + public OtherAggregateRemoved ExternalToLocal(string external) + { + return JsonSerialization.Deserialized(external); + } +} diff --git a/src/test/resources/text-expectations/c_sharp/other-aggregate-updated-adapter.text b/src/test/resources/text-expectations/c_sharp/other-aggregate-updated-adapter.text new file mode 100644 index 00000000..0e2a0ecf --- /dev/null +++ b/src/test/resources/text-expectations/c_sharp/other-aggregate-updated-adapter.text @@ -0,0 +1,39 @@ +using io.vlingo.xoom.otherapp.event.OtherAggregateUpdated; + +namespace Io.Vlingo.Xoomapp.Infrastructure.Exchange; + +/** + * See ExchangeAdapter + */ +public class OtherAggregateUpdatedAdapter : IExchangeAdapter +{ + + private string _supportedSchemaName; + + public OtherAggregateUpdatedAdapter(string supportedSchemaName) + { + _supportedSchemaName = supportedSchemaName; + } + + public OtherAggregateUpdated FromExchange(Message exchangeMessage) + { + return new OtherAggregateUpdatedMapper().ExternalToLocal(exchangeMessage.PayloadAsText()); + } + + public Message ToExchange(OtherAggregateUpdated local) + { + var messagePayload = new OtherAggregateUpdatedMapper().LocalToExternal(local); + return new Message(messagePayload, MessageParameters.Bare().DeliveryMode(DeliveryMode.Durable)); + } + + public bool supports(Object exchangeMessage) + { + if(typeof(exchangeMessage) != typeof(Message)) + { + return false; + } + var schemaName = ((Message) exchangeMessage).MessageParameters.TypeName(); + return _supportedSchemaName.EqualsIgnoreCase(schemaName); + } + +} diff --git a/src/test/resources/text-expectations/c_sharp/other-aggregate-updated-mapper.text b/src/test/resources/text-expectations/c_sharp/other-aggregate-updated-mapper.text new file mode 100644 index 00000000..4ab37bc0 --- /dev/null +++ b/src/test/resources/text-expectations/c_sharp/other-aggregate-updated-mapper.text @@ -0,0 +1,21 @@ +using io.vlingo.xoom.otherapp.event.OtherAggregateUpdated; +using Vlingo.Xoom.Common.Serialization; + +namespace Io.Vlingo.Xoomapp.Infrastructure.Exchange; + +/** + * See ExchangeMapper + */ +public class OtherAggregateUpdatedMapper : IExchangeMapper +{ + + public string LocalToExternal(OtherAggregateUpdated local) + { + return JsonSerialization.Serialized(local); + } + + public OtherAggregateUpdated ExternalToLocal(string external) + { + return JsonSerialization.Deserialized(external); + } +}