diff --git a/spring-modulith-events/pom.xml b/spring-modulith-events/pom.xml index d733693c..8db7da92 100644 --- a/spring-modulith-events/pom.xml +++ b/spring-modulith-events/pom.xml @@ -25,6 +25,7 @@ spring-modulith-events-messaging spring-modulith-events-mongodb spring-modulith-events-neo4j + spring-modulith-events-scs diff --git a/spring-modulith-events/spring-modulith-events-scs/README.md b/spring-modulith-events/spring-modulith-events-scs/README.md new file mode 100644 index 00000000..6a2ba7d4 --- /dev/null +++ b/spring-modulith-events/spring-modulith-events-scs/README.md @@ -0,0 +1,132 @@ +# Spring-Modulith Events Externalizer for Spring Cloud Stream + +[![Maven Central](https://img.shields.io/maven-central/v/io.zenwave360.sdk/spring-modulith-events-scs.svg?label=Maven%20Central&logo=apachemaven)](https://search.maven.org/artifact/io.zenwave360.sdk/spring-modulith-events-scs) +[![build](https://github.com/ZenWave360/spring-modulith-events-spring-cloud-stream/workflows/Build/badge.svg)](https://github.com/ZenWave360/spring-modulith-events-spring-cloud-stream/actions/workflows/build.yml) +[![coverage](https://raw.githubusercontent.com/ZenWave360/spring-modulith-events-spring-cloud-stream/badges/jacoco.svg)](https://github.com/ZenWave360/spring-modulith-events-spring-cloud-stream/actions/workflows/build.yml) +[![branches coverage](https://raw.githubusercontent.com/ZenWave360/spring-modulith-events-spring-cloud-stream/badges/branches.svg)](https://github.com/ZenWave360/spring-modulith-events-spring-cloud-stream/actions/workflows/build.yml) +[![GitHub](https://img.shields.io/github/license/ZenWave360/spring-modulith-events-spring-cloud-stream)](https://github.com/ZenWave360/spring-modulith-events-spring-cloud-stream/blob/main/LICENSE) + +Spring-Modulith Events Externalizer that uses Spring Cloud Stream supporting both JSON and Avro serialization formats. + +## Getting Started + +### Dependency +Add the following Maven dependency to your project: + +```xml + + io.zenwave360.sdk + spring-modulith-events-scs + ${spring-modulith-events-scs.version} + +``` + +### Configuration +Use `@EnableSpringCloudStreamEventExternalization` annotation to enable Spring Cloud Stream event externalization in your Spring configuration: + +```java +@Configuration +@EnableSpringCloudStreamEventExternalization +public class SpringCloudStreamEventsConfig { + // Additional configurations (if needed) +} +``` + +This configuration ensures that, in addition to events annotated with `@Externalized`, all events of type `org.springframework.messaging.Message` with a header named `SpringCloudStreamEventExternalizer.SPRING_CLOUD_STREAM_EVENT_HEADER` will be externalized and routed to their specified destination using the value of this header as the routing target. + +--- + +## Event Serialization + +Using the transactional event publication log requires serializing events to a format that can be stored in a database. Since the generic type of `Message` payload is lost when using the default `JacksonEventSerializer`, this library adds an extra `_class` field to preserve payload type information, allowing for complete deserialization to its original type. + +This library provides support for POJO (JSON) and Avro serialization formats for `Message` payloads. + +### Avro Serialization + +Avro serialization needs `com.fasterxml.jackson.dataformat.avro.AvroMapper` class present in the classpath. In order to use Avro serialization, you need to add the following dependency to your project: + +```xml + + com.fasterxml.jackson.dataformat + jackson-dataformat-avro + +``` + +--- + +## Routing Events + +### Programmatic Routing for `Message events + +You can define routing targets programmatically using a Message header: + +```java +public class CustomerEventsProducer implements ICustomerEventsProducer { + + private final ApplicationEventPublisher applicationEventPublisher; + + public void onCustomerCreated(CustomerCreated event) { + Message message = MessageBuilder.withPayload(event) + .setHeader( + SpringCloudStreamEventExternalizer.SPRING_CLOUD_STREAM_SENDTO_DESTINATION_HEADER, + "customer-created") // <- target binding name + .build(); + applicationEventPublisher.publishEvent(message); + } +} +``` + +### Annotation-Based Routing for POJO Events + +Leverage the `@Externalized` annotation to define the target binding name and routing key: + +```java +@Externalized("customer-created::#{#this.getLastname()}") +class CustomerCreated { + + public String getLastname() { + // Return the customer's last name + } +} +``` + +### Configure Spring Cloud Stream destination + +Configure Spring Cloud Stream destination for your bindings as usual in `application.yml`: + +```yaml +spring: + cloud: + stream: + bindings: + customer-created: + destination: customer-created-topic +``` + +### Routing Key + +`SpringCloudStreamEventExternalizer` dynamically sets the appropriate Message header (e.g., `kafka_messageKey` or `rabbit_routingKey`) from your routing key based on the channel binder type, if the routing header is not already present. + +- KafkaMessageChannelBinder: `kafka_messageKey` +- RabbitMessageChannelBinder: `rabbit_routingKey` +- KinesisMessageChannelBinder: `partitionKey` +- PubSubMessageChannelBinder: `pubsub_orderingKey` +- EventHubsMessageChannelBinder: `partitionKey` +- SolaceMessageChannelBinder: `solace_messageKey` +- PulsarMessageChannelBinder: `pulsar_key` + +--- + +## Using Snapshot Versions +In order to test snapshot versions of this library, add the following repository to your Maven configuration: + +```xml + + gh + https://raw.githubusercontent.com/ZenWave360/maven-snapshots/refs/heads/main + + true + + +``` diff --git a/spring-modulith-events/spring-modulith-events-scs/pom.xml b/spring-modulith-events/spring-modulith-events-scs/pom.xml new file mode 100644 index 00000000..1e81714c --- /dev/null +++ b/spring-modulith-events/spring-modulith-events-scs/pom.xml @@ -0,0 +1,149 @@ + + + + 4.0.0 + + + org.springframework.modulith + spring-modulith-events + 1.4.0-SNAPSHOT + + + Spring Modulith - Events - Spring Cloud Stream support + spring-modulith-events-scs + + + org.springframework.modulith.events.scs + + + 3.4.0 + 2024.0.0 + 2.2.1.RELEASE + 1.11.4 + + 3.0.0-M5 + + + + + Ivan Garcia Sainz-Aja + ivangsa@gmail.com + ZenWave360 + https://github.com/ZenWave360 + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + + org.springframework.modulith + spring-modulith-api + ${project.parent.version} + + + org.springframework.modulith + spring-modulith-events-core + ${project.parent.version} + + + org.springframework.cloud + spring-cloud-stream + + + + + org.apache.avro + avro + ${avro.version} + true + + + com.fasterxml.jackson.dataformat + jackson-dataformat-avro + true + + + com.fasterxml.jackson.core + jackson-databind + true + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-jdbc + test + + + com.h2database + h2 + test + + + + org.springframework.cloud + spring-cloud-starter-stream-kafka + test + + + org.springframework.cloud + spring-cloud-stream-schema + ${spring-cloud-stream-schema.version} + test + + + org.springframework.kafka + spring-kafka-test + test + + + + org.springframework.modulith + spring-modulith-starter-jdbc + ${parent.version} + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + alphabetical + + **/*IT* + **/*IntTest* + + + + + + + diff --git a/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/AvroEventSerializer.java b/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/AvroEventSerializer.java new file mode 100644 index 00000000..8a5eced8 --- /dev/null +++ b/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/AvroEventSerializer.java @@ -0,0 +1,30 @@ +package org.springframework.modulith.events.scs; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import org.springframework.modulith.events.core.EventSerializer; + +import java.util.Map; + +public class AvroEventSerializer extends MessageEventSerializer implements EventSerializer { + + private AvroMapper avroMapper; + + public AvroEventSerializer(ObjectMapper jacksonMapper) { + super(jacksonMapper); + this.avroMapper = AvroMapper.builder().build(); + } + + public AvroEventSerializer(AvroMapper avroMapper, ObjectMapper jacksonMapper) { + super(jacksonMapper); + this.avroMapper = avroMapper; + } + + protected Map serializeToMap(Object payload) { + ObjectNode objectNode = avroMapper.valueToTree(payload); + objectNode.remove("specificData"); // TODO: remove this recursively + return avroMapper.convertValue(objectNode, Map.class); + } + +} diff --git a/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/MessageEventSerializer.java b/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/MessageEventSerializer.java new file mode 100644 index 00000000..ca388be1 --- /dev/null +++ b/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/MessageEventSerializer.java @@ -0,0 +1,160 @@ +package org.springframework.modulith.events.scs; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.TreeNode; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.modulith.events.core.EventSerializer; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class MessageEventSerializer implements EventSerializer { + + protected Logger log = LoggerFactory.getLogger(getClass()); + + private final ObjectMapper jacksonMapper; + + /** + * Controls whether to store headers types to be used for deserialization. Default is true. + */ + private boolean processHeaderTypes = true; + + public MessageEventSerializer(ObjectMapper jacksonMapper) { + this.jacksonMapper = jacksonMapper; + } + + public void setProcessHeaderTypes(boolean processHeaderTypes) { + this.processHeaderTypes = processHeaderTypes; + } + + /** + * Convert the payload to a Map so it can be serialized to JSON. + *

+ * Subclasses (i.e AvroSerializer) can override this method to customize the serialization. + * + * @param payload + * @return + */ + protected Map serializeToMap(Object payload) { + ObjectNode objectNode = jacksonMapper.valueToTree(payload); + return jacksonMapper.convertValue(objectNode, Map.class); + } + + @Override + public Object serialize(Object event) { + if (event instanceof Message message) { + Map serializedMessage = new HashMap<>(); + serializedMessage.put("headers", message.getHeaders()); + serializedMessage.put("_header_types", extractHeaderTypes(message.getHeaders())); + + var payload = serializeToMap(message.getPayload()); + payload.put("_class", message.getPayload().getClass().getName()); + serializedMessage.put("payload", payload); + + return jacksonSerialize(serializedMessage); + } + return jacksonSerialize(event); + } + + @Override + public T deserialize(Object serialized, Class type) { + try { + return unsafeDeserialize(serialized, type); + } + catch (JsonProcessingException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + private T unsafeDeserialize(Object serialized, Class type) + throws JsonProcessingException, ClassNotFoundException { + if (Message.class.isAssignableFrom(type)) { + JsonNode node = jacksonMapper.readTree(serialized.toString()); + JsonNode headersNode = node.get("headers"); + Map headers = jacksonMapper.convertValue(headersNode, Map.class); + + if (processHeaderTypes) { + JsonNode headerTypesNode = node.get("_header_types"); + Map headerTypes = jacksonMapper.convertValue(headerTypesNode, Map.class); + processHeaderTypes(headerTypes, headers); + } + + JsonNode payloadNode = node.get("payload"); + Object payload = null; + if (payloadNode.get("_class") != null) { + Class payloadType = Class.forName(payloadNode.get("_class").asText()); + if (payloadNode instanceof ObjectNode objectNode) { + objectNode.remove("_class"); + } + payload = deserializePayload(payloadNode, payloadType); + } + else { + payload = deserializePayload(payloadNode, Object.class); + } + return (T) MessageBuilder.createMessage(payload, new MessageHeaders(headers)); + } + return jacksonDeserialize(serialized, type); + } + + protected T deserializePayload(TreeNode payloadNode, Class payloadType) throws JsonProcessingException { + return jacksonMapper.treeToValue(payloadNode, payloadType); + } + + protected Object jacksonSerialize(Object event) { + try { + var map = serializeToMap(event); + return jacksonMapper.writeValueAsString(map); + } + catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + protected T jacksonDeserialize(Object serialized, Class type) { + try { + JsonNode node = jacksonMapper.readTree(serialized.toString()); + return (T) jacksonMapper.readerFor(type).readValue(node); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + + protected Map extractHeaderTypes(Map headers) { + Map headerTypes = new HashMap<>(); + headers.forEach((key, value) -> headerTypes.put(key, value.getClass().getName())); + return headerTypes; + } + + protected void processHeaderTypes(Map headerTypes, Map headers) { + if (headerTypes == null) { + return; + } + headers.forEach((key, value) -> { + if (headerTypes.containsKey(key)) { + var headerType = headerTypes.get(key); + try { + if (value instanceof String) { + headers.put(key, jacksonMapper.convertValue(value, Class.forName(headerType))); + } + else { + headers.put(key, jacksonDeserialize(value, Class.forName(headerType))); + } + } + catch (Exception e) { + log.error("Failed to process header: {key: {}, value: {}, type: {}}. Error: {}", key, value, + headerType, e.getMessage()); + } + } + }); + } + +} diff --git a/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/SpringCloudStreamEventExternalizer.java b/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/SpringCloudStreamEventExternalizer.java new file mode 100644 index 00000000..16dfac75 --- /dev/null +++ b/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/SpringCloudStreamEventExternalizer.java @@ -0,0 +1,98 @@ +package org.springframework.modulith.events.scs; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cloud.stream.binder.BinderFactory; +import org.springframework.cloud.stream.config.BindingServiceProperties; +import org.springframework.cloud.stream.function.StreamBridge; +import org.springframework.expression.EvaluationContext; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.modulith.events.EventExternalizationConfiguration; +import org.springframework.modulith.events.RoutingTarget; +import org.springframework.modulith.events.support.BrokerRouting; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiFunction; + +public class SpringCloudStreamEventExternalizer implements BiFunction> { + + private static final Logger log = LoggerFactory.getLogger(SpringCloudStreamEventExternalizer.class); + + public static final String SPRING_CLOUD_STREAM_SENDTO_DESTINATION_HEADER = "spring.cloud.stream.sendto.destination"; + + private final EventExternalizationConfiguration configuration; + + private final StreamBridge streamBridge; + + private final BindingServiceProperties bindingServiceProperties; + + private final BinderFactory binderFactory; + + private final EvaluationContext context; + + public SpringCloudStreamEventExternalizer(EventExternalizationConfiguration configuration, + EvaluationContext context, StreamBridge streamBridge, BindingServiceProperties bindingServiceProperties, + BinderFactory binderFactory) { + this.configuration = configuration; + this.context = context; + this.streamBridge = streamBridge; + this.bindingServiceProperties = bindingServiceProperties; + this.binderFactory = binderFactory; + } + + @Override + public CompletableFuture apply(RoutingTarget routingTarget, Object event) { + var routing = BrokerRouting.of(routingTarget, context); + + var target = getTarget(event, routingTarget); + var keyHeaderValue = routing.getKey(event); + var keyHeaderName = getKeyHeaderName(target, bindingServiceProperties, binderFactory); + + var headersMap = event instanceof Message ? new LinkedHashMap<>(((Message) event).getHeaders()) + : new LinkedHashMap(); + if (keyHeaderValue != null && keyHeaderName != null) { + if (!headersMap.containsKey(keyHeaderName)) { + log.debug("Adding key header to message: {} = {}", keyHeaderName, keyHeaderValue); + headersMap.put(keyHeaderName, keyHeaderValue); + } + } + var payload = event instanceof Message ? ((Message) event).getPayload() : event; + var message = MessageBuilder.withPayload(payload).copyHeaders(headersMap).build(); + + log.debug("Sending event to Spring Cloud Stream target: {}", target); + var result = streamBridge.send(target, message); + return CompletableFuture.completedFuture(result); + } + + protected static final Map messageKeyHeaders = Map.of( + "org.springframework.cloud.stream.binder.kafka.KafkaMessageChannelBinder", "kafka_messageKey", + "org.springframework.cloud.stream.binder.rabbit.RabbitMessageChannelBinder", "rabbit_routingKey", + "org.springframework.cloud.stream.binder.kinesis.KinesisMessageChannelBinder", "partitionKey", + "org.springframework.cloud.stream.binder.pubsub.PubSubMessageChannelBinder", "pubsub_orderingKey", + "org.springframework.cloud.stream.binder.eventhubs.EventHubsMessageChannelBinder", "partitionKey", + "org.springframework.cloud.stream.binder.solace.SolaceMessageChannelBinder", "solace_messageKey", + "org.springframework.cloud.stream.binder.pulsar.PulsarMessageChannelBinder", "pulsar_key"); + + protected String getKeyHeaderName(String channelName, BindingServiceProperties bindingServiceProperties, + BinderFactory binderFactory) { + String binderConfigurationName = bindingServiceProperties.getBinder(channelName); + var binder = binderFactory.getBinder(binderConfigurationName, MessageChannel.class); + if (binder == null) { + return null; + } + return messageKeyHeaders.get(binder.getClass().getName()); + } + + protected String getTarget(Object event, RoutingTarget routingTarget) { + if (event instanceof Message message) { + var target = message.getHeaders().get(SPRING_CLOUD_STREAM_SENDTO_DESTINATION_HEADER, String.class); + return target != null ? target : routingTarget.getTarget(); + } + return routingTarget.getTarget(); + } + +} diff --git a/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/config/EnableSpringCloudStreamEventExternalization.java b/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/config/EnableSpringCloudStreamEventExternalization.java new file mode 100644 index 00000000..36d5a548 --- /dev/null +++ b/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/config/EnableSpringCloudStreamEventExternalization.java @@ -0,0 +1,18 @@ +package org.springframework.modulith.events.scs.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Configuration +@Import({ SpringCloudStreamEventExternalizerConfiguration.class, MessageEventSerializerConfiguration.class, + MessageExternalizationConfiguration.class }) +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface EnableSpringCloudStreamEventExternalization { + +} diff --git a/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/config/MessageEventSerializerConfiguration.java b/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/config/MessageEventSerializerConfiguration.java new file mode 100644 index 00000000..3c208772 --- /dev/null +++ b/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/config/MessageEventSerializerConfiguration.java @@ -0,0 +1,38 @@ +package org.springframework.modulith.events.scs.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import org.springframework.context.annotation.Configuration; +import org.springframework.modulith.events.scs.AvroEventSerializer; +import org.springframework.modulith.events.scs.MessageEventSerializer; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; +import org.springframework.modulith.events.config.EventExternalizationAutoConfiguration; +import org.springframework.modulith.events.core.EventSerializer; + +@Configuration +@AutoConfigureAfter(EventExternalizationAutoConfiguration.class) +@ConditionalOnProperty(name = "spring.modulith.events.externalization.enabled", havingValue = "true", + matchIfMissing = true) +public class MessageEventSerializerConfiguration { + + @Bean + @Primary + @ConditionalOnClass(AvroMapper.class) + public EventSerializer avroEventSerializer(ObjectMapper mapper) { + return new AvroEventSerializer(mapper); + } + + @Bean + @Primary + @ConditionalOnMissingClass("com.fasterxml.jackson.dataformat.avro.AvroMapper") + public EventSerializer messageEventSerializer(ObjectMapper mapper) { + return new MessageEventSerializer(mapper); + } + +} diff --git a/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/config/MessageExternalizationConfiguration.java b/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/config/MessageExternalizationConfiguration.java new file mode 100644 index 00000000..6e7d3034 --- /dev/null +++ b/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/config/MessageExternalizationConfiguration.java @@ -0,0 +1,36 @@ +package org.springframework.modulith.events.scs.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.modulith.events.scs.SpringCloudStreamEventExternalizer; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.messaging.Message; +import org.springframework.modulith.events.EventExternalizationConfiguration; +import org.springframework.modulith.events.RoutingTarget; +import org.springframework.modulith.events.config.EventExternalizationAutoConfiguration; + +@Configuration +@AutoConfigureAfter(EventExternalizationAutoConfiguration.class) +@ConditionalOnProperty(name = "spring.modulith.events.externalization.enabled", havingValue = "true", + matchIfMissing = true) +public class MessageExternalizationConfiguration { + + @Bean + EventExternalizationConfiguration eventExternalizationConfiguration() { + return EventExternalizationConfiguration.externalizing() + .select(event -> EventExternalizationConfiguration.annotatedAsExternalized().test(event) + || event instanceof Message && getTarget(event) != null) + .route(Message.class, event -> RoutingTarget.forTarget(getTarget(event)).withoutKey()) + .build(); + } + + private String getTarget(Object event) { + if (event instanceof Message message) { + return message.getHeaders() + .get(SpringCloudStreamEventExternalizer.SPRING_CLOUD_STREAM_SENDTO_DESTINATION_HEADER, String.class); + } + return null; + } +} diff --git a/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/config/SpringCloudStreamEventExternalizerConfiguration.java b/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/config/SpringCloudStreamEventExternalizerConfiguration.java new file mode 100644 index 00000000..d02a680f --- /dev/null +++ b/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/config/SpringCloudStreamEventExternalizerConfiguration.java @@ -0,0 +1,59 @@ +/* + * Copyright 2023-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.modulith.events.scs.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.modulith.events.scs.SpringCloudStreamEventExternalizer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cloud.stream.binder.BinderFactory; +import org.springframework.cloud.stream.config.BindingServiceProperties; +import org.springframework.cloud.stream.function.StreamBridge; +import org.springframework.context.annotation.Bean; +import org.springframework.context.expression.BeanFactoryResolver; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.modulith.events.EventExternalizationConfiguration; +import org.springframework.modulith.events.config.EventExternalizationAutoConfiguration; +import org.springframework.modulith.events.support.DelegatingEventExternalizer; + +@Configuration +@AutoConfigureAfter(EventExternalizationAutoConfiguration.class) +@ConditionalOnClass(StreamBridge.class) +@ConditionalOnProperty(name = "spring.modulith.events.externalization.enabled", havingValue = "true", + matchIfMissing = true) +public class SpringCloudStreamEventExternalizerConfiguration { + + private static final Logger log = LoggerFactory.getLogger(SpringCloudStreamEventExternalizerConfiguration.class); + + @Bean + DelegatingEventExternalizer springCloudStreamMessageExternalizer(EventExternalizationConfiguration configuration, + StreamBridge streamBridge, BeanFactory factory, BindingServiceProperties bindingServiceProperties, + BinderFactory binderFactory) { + log.debug("Registering domain event externalization to Spring Cloud Stream…"); + + var context = new StandardEvaluationContext(); + context.setBeanResolver(new BeanFactoryResolver(factory)); + + return new DelegatingEventExternalizer(configuration, new SpringCloudStreamEventExternalizer(configuration, + context, streamBridge, bindingServiceProperties, binderFactory)); + } + +} diff --git a/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/package-info.java b/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/package-info.java new file mode 100644 index 00000000..992b3b00 --- /dev/null +++ b/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/package-info.java @@ -0,0 +1,5 @@ +/** + * Spring Cloud Stream event externalization support. + */ +@org.springframework.lang.NonNullApi +package org.springframework.modulith.events.scs; diff --git a/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/AvroEventSerializerTest.java b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/AvroEventSerializerTest.java new file mode 100644 index 00000000..2d673be5 --- /dev/null +++ b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/AvroEventSerializerTest.java @@ -0,0 +1,94 @@ +package org.springframework.modulith.events.scs; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.modulith.events.scs.dtos.avro.CustomerEvent; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.messaging.Message; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.modulith.events.core.EventSerializer; + +public class AvroEventSerializerTest { + + private EventSerializer eventSerializer; + + @BeforeEach + public void setUp() { + eventSerializer = new AvroEventSerializer(new ObjectMapper()); + } + + @Test + public void testSerializeMessage() { + var customerEvent = new CustomerEvent(); + customerEvent.setName("John Doe"); + Message message = MessageBuilder.withPayload(customerEvent).setHeader("headerKey", "headerValue").build(); + + Object serialized = eventSerializer.serialize(message); + + Assertions.assertTrue(serialized instanceof String); + Assertions.assertTrue(serialized.toString().contains("\"name\":\"John Doe\",")); + Assertions.assertTrue(serialized.toString() + .contains("\"_class\":\"org.springframework.modulith.events.scs.dtos.avro.CustomerEvent\"")); + } + + @Test + public void testDeserializeObject() { + String serializedObject = """ + { + "name" : "John Doe" + } + """; + CustomerEvent deserialized = eventSerializer.deserialize(serializedObject, CustomerEvent.class); + + Assertions.assertEquals("John Doe", deserialized.getName()); + } + + @Test + public void testDeserializeMessage() throws JsonProcessingException, ClassNotFoundException { + String serializedMessage = """ + { + "headers" : { + "headerKey" : "headerValue", + "id" : "cc76cfb5-4ba3-5bf1-f652-9f44dfc24c85", + "timestamp" : 1735318992520 + }, + "payload" : { + "name" : "John Doe", + "addresses" : [ ], + "paymentMethods" : [ ], + "_class" : "org.springframework.modulith.events.scs.dtos.avro.CustomerEvent" + } + } + """; + Message deserialized = eventSerializer.deserialize(serializedMessage, Message.class); + + Assertions.assertTrue(deserialized.getPayload() instanceof CustomerEvent); + } + + @Test + public void testSerializeDeserializeMessage() throws JsonProcessingException, ClassNotFoundException { + var customerEvent = new CustomerEvent(); + customerEvent.setName("John Doe"); + Message message = MessageBuilder.withPayload(customerEvent).setHeader("headerKey", "headerValue").build(); + + Object serialized = eventSerializer.serialize(message); + Message deserialized = eventSerializer.deserialize(serialized, Message.class); + + Assertions.assertEquals(message.getPayload().getClass(), deserialized.getPayload().getClass()); + } + + @Test + public void testSerializeDeserializeObject() throws JsonProcessingException, ClassNotFoundException { + var customerEvent = new CustomerEvent(); + customerEvent.setName("John Doe"); + + Object serialized = eventSerializer.serialize(customerEvent); + Object deserialized = eventSerializer.deserialize(serialized, CustomerEvent.class); + + Assertions.assertEquals(CustomerEvent.class, deserialized.getClass()); + Assertions.assertEquals(customerEvent.getName(), ((CustomerEvent) deserialized).getName()); + } + +} diff --git a/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/MessageEventSerializerTest.java b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/MessageEventSerializerTest.java new file mode 100644 index 00000000..3047feaf --- /dev/null +++ b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/MessageEventSerializerTest.java @@ -0,0 +1,109 @@ +package org.springframework.modulith.events.scs; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.modulith.events.scs.dtos.json.CustomerEvent; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.messaging.Message; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.modulith.events.core.EventSerializer; + +import java.math.BigDecimal; +import java.util.UUID; + +public class MessageEventSerializerTest { + + private ObjectMapper objectMapper; + + private EventSerializer eventSerializer; + + @BeforeEach + public void setUp() { + objectMapper = new ObjectMapper(); + eventSerializer = new MessageEventSerializer(objectMapper); + } + + @Test + public void testSerializeMessage() { + var customerEvent = new CustomerEvent().withName("John Doe"); + Message message = MessageBuilder.withPayload(customerEvent).setHeader("headerKey", "headerValue").build(); + + Object serialized = eventSerializer.serialize(message); + + Assertions.assertTrue(serialized instanceof String); + Assertions.assertTrue(serialized.toString().contains("\"payload\":{\"name\":\"John Doe\",")); + Assertions.assertTrue(serialized.toString() + .contains("\"_class\":\"org.springframework.modulith.events.scs.dtos.json.CustomerEvent\"")); + } + + @Test + public void testSerializeObject() { + String serializedObject = """ + { + "name" : "John Doe" + } + """; + CustomerEvent deserialized = eventSerializer.deserialize(serializedObject, CustomerEvent.class); + + Assertions.assertEquals("John Doe", deserialized.getName()); + } + + @Test + public void testDeserializeMessage() throws JsonProcessingException, ClassNotFoundException { + String serializedMessage = """ + { + "headers" : { + "headerKey" : "headerValue", + "id" : "cc76cfb5-4ba3-5bf1-f652-9f44dfc24c85", + "timestamp" : 1735318992520 + }, + "_header_types" : { + "headerkey" : "java.lang.String", + "id" : "java.util.UUID", + "timestamp" : "java.lang.Long" + }, + "payload" : { + "name" : "John Doe", + "addresses" : [ ], + "paymentMethods" : [ ], + "_class" : "org.springframework.modulith.events.scs.dtos.json.CustomerEvent" + } + } + """; + Message deserialized = eventSerializer.deserialize(serializedMessage, Message.class); + + Assertions.assertTrue(deserialized.getPayload() instanceof CustomerEvent); + Assertions.assertTrue(deserialized.getHeaders().get("id") instanceof UUID); + } + + @Test + public void testSerializeDeserializeMessage() throws JsonProcessingException, ClassNotFoundException { + var customerEvent = new CustomerEvent().withName("John Doe"); + Message message = MessageBuilder.withPayload(customerEvent) + .setHeader("headerKey", "headerValue") + .setHeader("uuid", UUID.randomUUID()) + .setHeader("long", System.currentTimeMillis()) + .setHeader("number", BigDecimal.ONE) + .build(); + + Object serialized = eventSerializer.serialize(message); + Message deserialized = eventSerializer.deserialize(serialized, Message.class); + + Assertions.assertEquals(message.getPayload().getClass(), deserialized.getPayload().getClass()); + Assertions.assertEquals(UUID.class, deserialized.getHeaders().get("uuid").getClass()); + Assertions.assertEquals(BigDecimal.class, deserialized.getHeaders().get("number").getClass()); + } + + @Test + public void testSerializeDeserializeObject() throws JsonProcessingException, ClassNotFoundException { + var customerEvent = new CustomerEvent().withName("John Doe"); + + Object serialized = eventSerializer.serialize(customerEvent); + Object deserialized = eventSerializer.deserialize(serialized, CustomerEvent.class); + + Assertions.assertEquals(CustomerEvent.class, deserialized.getClass()); + } + +} diff --git a/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/SCSAvroEventExternalizerTest.java b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/SCSAvroEventExternalizerTest.java new file mode 100644 index 00000000..87e70a9f --- /dev/null +++ b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/SCSAvroEventExternalizerTest.java @@ -0,0 +1,75 @@ +package org.springframework.modulith.events.scs; + +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.stream.function.StreamBridge; +import org.springframework.messaging.Message; +import org.springframework.modulith.events.scs.dtos.avro.*; +import org.springframework.test.context.bean.override.mockito.MockitoSpyBean; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.awaitility.Awaitility.await; +import static org.mockito.Mockito.verify; + +@SpringBootTest(classes = { TestsConfiguration.class }) +@Transactional +public class SCSAvroEventExternalizerTest { + + @Autowired + TestsConfiguration.CustomerEventsProducer customerEventsProducer; + + @MockitoSpyBean + private StreamBridge streamBridge; + + @Test + void testExternalizeAvroEvent() throws InterruptedException { + var event = new CustomerEvent(); + event.setId(1L); + event.setName("John Doe"); + event.setEmail("me@email.com"); + event.setAddresses(List.of(new Address("Main St", "City"))); + event.setPaymentMethods(List.of(new PaymentMethod(1, PaymentMethodType.MASTERCARD, "1234"))); + + customerEventsProducer.onCustomerEventAvroMessage(event); + + // Wait for the event to be externalized + await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> { + verify(streamBridge).send(Mockito.eq("customers-avro-out-0"), Mockito.argThat(message -> { + if (message instanceof Message) { + var payload = ((Message) message).getPayload(); + return payload instanceof CustomerEvent && "John Doe".equals(((CustomerEvent) payload).getName()); + } + return false; + })); + }); + } + + @Test + void testExternalizeAvroPojo() throws InterruptedException { + var event = new ExternalizedCustomerEvent(); + event.setId(1L); + event.setName("John Doe Externalized"); + event.setEmail("me@email.com"); + event.setAddresses(List.of(new Address("Main St", "City"))); + event.setPaymentMethods(List.of(new PaymentMethod(1, PaymentMethodType.MASTERCARD, "1234"))); + customerEventsProducer.onCustomerEventAvroPojo(event); + + // Wait for the event to be externalized + await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> { + verify(streamBridge).send(Mockito.eq("customers-avro-externalized-out-0"), Mockito.argThat(message -> { + if (message instanceof Message) { + var payload = ((Message) message).getPayload(); + return payload instanceof ExternalizedCustomerEvent + && "John Doe Externalized".equals(((ExternalizedCustomerEvent) payload).getName()); + } + return false; + })); + }); + } + +} diff --git a/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/SCSJsonEventExternalizerTest.java b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/SCSJsonEventExternalizerTest.java new file mode 100644 index 00000000..cdd734a4 --- /dev/null +++ b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/SCSJsonEventExternalizerTest.java @@ -0,0 +1,66 @@ +package org.springframework.modulith.events.scs; + +import org.springframework.modulith.events.scs.dtos.json.CustomerEvent; +import org.springframework.modulith.events.scs.dtos.json.ExternalizedCustomerEvent; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.stream.function.StreamBridge; +import org.springframework.messaging.Message; +import org.springframework.test.context.bean.override.mockito.MockitoSpyBean; +import org.springframework.transaction.annotation.Transactional; + +import java.util.concurrent.TimeUnit; + +import static org.awaitility.Awaitility.await; +import static org.mockito.Mockito.verify; + +@SpringBootTest(classes = { TestsConfiguration.class }) +@Transactional +public class SCSJsonEventExternalizerTest { + + @Autowired + TestsConfiguration.CustomerEventsProducer customerEventsProducer; + + @MockitoSpyBean + private StreamBridge streamBridge; + + @Test + void testExternalizeJsonMessage() throws InterruptedException { + var event = new CustomerEvent().withName("John Doe"); + customerEventsProducer.onCustomerEventJsonMessage(event); + + // Wait for the event to be externalized + await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> { + verify(streamBridge).send(Mockito.eq("customers-json-out-0"), Mockito.argThat(message -> { + if (message instanceof Message) { + var payload = ((Message) message).getPayload(); + return payload instanceof CustomerEvent + && "John Doe".equals(((CustomerEvent) payload).getName()); + } + return false; + })); + }); + } + + @Test + void testExternalizeJsonPojo() throws InterruptedException { + var event = new ExternalizedCustomerEvent().withName("John Doe Externalized"); + customerEventsProducer.onCustomerEventJsonPojo(event); + + // Wait for the event to be externalized + await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> { + verify(streamBridge).send(Mockito.eq("customers-json-externalized-out-0"), Mockito.argThat(message -> { + if (message instanceof Message) { + var payload = ((Message) message).getPayload(); + return payload instanceof CustomerEvent + && "John Doe Externalized".equals(((CustomerEvent) payload).getName()); + } + return false; + })); + }); + } + + +} diff --git a/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/TestsConfiguration.java b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/TestsConfiguration.java new file mode 100644 index 00000000..3fb9624e --- /dev/null +++ b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/TestsConfiguration.java @@ -0,0 +1,87 @@ +package org.springframework.modulith.events.scs; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.cloud.stream.schema.avro.AvroSchemaMessageConverter; +import org.springframework.cloud.stream.schema.avro.AvroSchemaServiceManagerImpl; +import org.springframework.modulith.events.scs.config.EnableSpringCloudStreamEventExternalization; +import org.springframework.modulith.events.scs.dtos.avro.CustomerEvent; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.annotation.Bean; +import org.springframework.kafka.test.EmbeddedKafkaBroker; +import org.springframework.kafka.test.EmbeddedKafkaZKBroker; +import org.springframework.kafka.test.context.EmbeddedKafka; +import org.springframework.messaging.Message; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.modulith.events.scs.dtos.avro.ExternalizedCustomerEvent; +import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +@EnableAutoConfiguration +@EnableSpringCloudStreamEventExternalization +@EmbeddedKafka(partitions = 1) +@EnableTransactionManagement +public class TestsConfiguration { + + @Bean + public ObjectMapper objectMapper() { + return new ObjectMapper(); // Customize if needed + } + + @Bean + EmbeddedKafkaBroker embeddedKafkaBroker() { + return new EmbeddedKafkaZKBroker(1, true, 1); + } + + @Bean + CustomerEventsProducer customerEventsProducer(ApplicationEventPublisher applicationEventPublisher) { + return new CustomerEventsProducer(applicationEventPublisher); + } + + @Bean + public static AvroSchemaMessageConverter avroSchemaMessageConverter() { + return new AvroSchemaMessageConverter(new AvroSchemaServiceManagerImpl()); + } + + static class CustomerEventsProducer { + + private final ApplicationEventPublisher applicationEventPublisher; + + public CustomerEventsProducer(ApplicationEventPublisher applicationEventPublisher) { + this.applicationEventPublisher = applicationEventPublisher; + } + + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void onCustomerEventJsonMessage(org.springframework.modulith.events.scs.dtos.json.CustomerEvent event) { + Message message = MessageBuilder + .withPayload(event) + .setHeader(SpringCloudStreamEventExternalizer.SPRING_CLOUD_STREAM_SENDTO_DESTINATION_HEADER, + "customers-json-out-0") // <- target binding name + .build(); + applicationEventPublisher.publishEvent(message); + } + + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void onCustomerEventJsonPojo(org.springframework.modulith.events.scs.dtos.json.CustomerEvent event) { + applicationEventPublisher.publishEvent(event); + } + + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void onCustomerEventAvroMessage(CustomerEvent event) { + Message message = MessageBuilder + .withPayload(event) + .setHeader(SpringCloudStreamEventExternalizer.SPRING_CLOUD_STREAM_SENDTO_DESTINATION_HEADER, + "customers-avro-out-0") // <- target binding name + .build(); + applicationEventPublisher.publishEvent(message); + } + + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void onCustomerEventAvroPojo(ExternalizedCustomerEvent event) { + applicationEventPublisher.publishEvent(event); + } + + } + +} diff --git a/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/avro/Address.java b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/avro/Address.java new file mode 100644 index 00000000..c6f45596 --- /dev/null +++ b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/avro/Address.java @@ -0,0 +1,412 @@ +/** + * Autogenerated by Avro + * + * DO NOT EDIT DIRECTLY + */ +package org.springframework.modulith.events.scs.dtos.avro; + +import org.apache.avro.message.BinaryMessageDecoder; +import org.apache.avro.message.BinaryMessageEncoder; +import org.apache.avro.message.SchemaStore; +import org.apache.avro.specific.SpecificData; +import org.apache.avro.util.Utf8; + +@org.apache.avro.specific.AvroGenerated +public class Address extends org.apache.avro.specific.SpecificRecordBase + implements org.apache.avro.specific.SpecificRecord { + + private static final long serialVersionUID = 7121751969643202111L; + + public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse( + "{\"type\":\"record\",\"name\":\"Address\",\"namespace\":\"org.springframework.modulith.events.scs.dtos.avro\",\"fields\":[{\"name\":\"street\",\"type\":\"string\"},{\"name\":\"city\",\"type\":\"string\"}]}"); + + public static org.apache.avro.Schema getClassSchema() { + return SCHEMA$; + } + + private static final SpecificData MODEL$ = new SpecificData(); + + private static final BinaryMessageEncoder

ENCODER = new BinaryMessageEncoder<>(MODEL$, SCHEMA$); + + private static final BinaryMessageDecoder
DECODER = new BinaryMessageDecoder<>(MODEL$, SCHEMA$); + + /** + * Return the BinaryMessageEncoder instance used by this class. + * @return the message encoder used by this class + */ + public static BinaryMessageEncoder
getEncoder() { + return ENCODER; + } + + /** + * Return the BinaryMessageDecoder instance used by this class. + * @return the message decoder used by this class + */ + public static BinaryMessageDecoder
getDecoder() { + return DECODER; + } + + /** + * Create a new BinaryMessageDecoder instance for this class that uses the specified + * {@link SchemaStore}. + * @param resolver a {@link SchemaStore} used to find schemas by fingerprint + * @return a BinaryMessageDecoder instance for this class backed by the given + * SchemaStore + */ + public static BinaryMessageDecoder
createDecoder(SchemaStore resolver) { + return new BinaryMessageDecoder<>(MODEL$, SCHEMA$, resolver); + } + + /** + * Serializes this Address to a ByteBuffer. + * @return a buffer holding the serialized data for this instance + * @throws java.io.IOException if this instance could not be serialized + */ + public java.nio.ByteBuffer toByteBuffer() throws java.io.IOException { + return ENCODER.encode(this); + } + + /** + * Deserializes a Address from a ByteBuffer. + * @param b a byte buffer holding serialized data for an instance of this class + * @return a Address instance decoded from the given buffer + * @throws java.io.IOException if the given bytes could not be deserialized into an + * instance of this class + */ + public static Address fromByteBuffer(java.nio.ByteBuffer b) throws java.io.IOException { + return DECODER.decode(b); + } + + private CharSequence street; + + private CharSequence city; + + /** + * Default constructor. Note that this does not initialize fields to their default + * values from the schema. If that is desired then one should use + * newBuilder(). + */ + public Address() { + } + + /** + * All-args constructor. + * @param street The new value for street + * @param city The new value for city + */ + public Address(CharSequence street, CharSequence city) { + this.street = street; + this.city = city; + } + + @Override + public SpecificData getSpecificData() { + return MODEL$; + } + + @Override + public org.apache.avro.Schema getSchema() { + return SCHEMA$; + } + + // Used by DatumWriter. Applications should not call. + @Override + public Object get(int field$) { + switch (field$) { + case 0: + return street; + case 1: + return city; + default: + throw new IndexOutOfBoundsException("Invalid index: " + field$); + } + } + + // Used by DatumReader. Applications should not call. + @Override + @SuppressWarnings(value = "unchecked") + public void put(int field$, Object value$) { + switch (field$) { + case 0: + street = (CharSequence) value$; + break; + case 1: + city = (CharSequence) value$; + break; + default: + throw new IndexOutOfBoundsException("Invalid index: " + field$); + } + } + + /** + * Gets the value of the 'street' field. + * @return The value of the 'street' field. + */ + public CharSequence getStreet() { + return street; + } + + /** + * Sets the value of the 'street' field. + * @param value the value to set. + */ + public void setStreet(CharSequence value) { + this.street = value; + } + + /** + * Gets the value of the 'city' field. + * @return The value of the 'city' field. + */ + public CharSequence getCity() { + return city; + } + + /** + * Sets the value of the 'city' field. + * @param value the value to set. + */ + public void setCity(CharSequence value) { + this.city = value; + } + + /** + * Creates a new Address RecordBuilder. + * @return A new Address RecordBuilder + */ + public static Address.Builder newBuilder() { + return new Address.Builder(); + } + + /** + * Creates a new Address RecordBuilder by copying an existing Builder. + * @param other The existing builder to copy. + * @return A new Address RecordBuilder + */ + public static Address.Builder newBuilder( + Address.Builder other) { + if (other == null) { + return new Address.Builder(); + } + else { + return new Address.Builder(other); + } + } + + /** + * Creates a new Address RecordBuilder by copying an existing Address instance. + * @param other The existing instance to copy. + * @return A new Address RecordBuilder + */ + public static Address.Builder newBuilder( + Address other) { + if (other == null) { + return new Address.Builder(); + } + else { + return new Address.Builder(other); + } + } + + /** + * RecordBuilder for Address instances. + */ + @org.apache.avro.specific.AvroGenerated + public static class Builder extends org.apache.avro.specific.SpecificRecordBuilderBase
+ implements org.apache.avro.data.RecordBuilder
{ + + private CharSequence street; + + private CharSequence city; + + /** Creates a new Builder */ + private Builder() { + super(SCHEMA$, MODEL$); + } + + /** + * Creates a Builder by copying an existing Builder. + * @param other The existing Builder to copy. + */ + private Builder(Address.Builder other) { + super(other); + if (isValidValue(fields()[0], other.street)) { + this.street = data().deepCopy(fields()[0].schema(), other.street); + fieldSetFlags()[0] = other.fieldSetFlags()[0]; + } + if (isValidValue(fields()[1], other.city)) { + this.city = data().deepCopy(fields()[1].schema(), other.city); + fieldSetFlags()[1] = other.fieldSetFlags()[1]; + } + } + + /** + * Creates a Builder by copying an existing Address instance + * @param other The existing instance to copy. + */ + private Builder(Address other) { + super(SCHEMA$, MODEL$); + if (isValidValue(fields()[0], other.street)) { + this.street = data().deepCopy(fields()[0].schema(), other.street); + fieldSetFlags()[0] = true; + } + if (isValidValue(fields()[1], other.city)) { + this.city = data().deepCopy(fields()[1].schema(), other.city); + fieldSetFlags()[1] = true; + } + } + + /** + * Gets the value of the 'street' field. + * @return The value. + */ + public CharSequence getStreet() { + return street; + } + + /** + * Sets the value of the 'street' field. + * @param value The value of 'street'. + * @return This builder. + */ + public Address.Builder setStreet(CharSequence value) { + validate(fields()[0], value); + this.street = value; + fieldSetFlags()[0] = true; + return this; + } + + /** + * Checks whether the 'street' field has been set. + * @return True if the 'street' field has been set, false otherwise. + */ + public boolean hasStreet() { + return fieldSetFlags()[0]; + } + + /** + * Clears the value of the 'street' field. + * @return This builder. + */ + public Address.Builder clearStreet() { + street = null; + fieldSetFlags()[0] = false; + return this; + } + + /** + * Gets the value of the 'city' field. + * @return The value. + */ + public CharSequence getCity() { + return city; + } + + /** + * Sets the value of the 'city' field. + * @param value The value of 'city'. + * @return This builder. + */ + public Address.Builder setCity(CharSequence value) { + validate(fields()[1], value); + this.city = value; + fieldSetFlags()[1] = true; + return this; + } + + /** + * Checks whether the 'city' field has been set. + * @return True if the 'city' field has been set, false otherwise. + */ + public boolean hasCity() { + return fieldSetFlags()[1]; + } + + /** + * Clears the value of the 'city' field. + * @return This builder. + */ + public Address.Builder clearCity() { + city = null; + fieldSetFlags()[1] = false; + return this; + } + + @Override + @SuppressWarnings("unchecked") + public Address build() { + try { + Address record = new Address(); + record.street = fieldSetFlags()[0] ? this.street : (CharSequence) defaultValue(fields()[0]); + record.city = fieldSetFlags()[1] ? this.city : (CharSequence) defaultValue(fields()[1]); + return record; + } + catch (org.apache.avro.AvroMissingFieldException e) { + throw e; + } + catch (Exception e) { + throw new org.apache.avro.AvroRuntimeException(e); + } + } + + } + + @SuppressWarnings("unchecked") + private static final org.apache.avro.io.DatumWriter
WRITER$ = (org.apache.avro.io.DatumWriter
) MODEL$ + .createDatumWriter(SCHEMA$); + + @Override + public void writeExternal(java.io.ObjectOutput out) throws java.io.IOException { + WRITER$.write(this, SpecificData.getEncoder(out)); + } + + @SuppressWarnings("unchecked") + private static final org.apache.avro.io.DatumReader
READER$ = (org.apache.avro.io.DatumReader
) MODEL$ + .createDatumReader(SCHEMA$); + + @Override + public void readExternal(java.io.ObjectInput in) throws java.io.IOException { + READER$.read(this, SpecificData.getDecoder(in)); + } + + @Override + protected boolean hasCustomCoders() { + return true; + } + + @Override + public void customEncode(org.apache.avro.io.Encoder out) throws java.io.IOException { + out.writeString(this.street); + + out.writeString(this.city); + + } + + @Override + public void customDecode(org.apache.avro.io.ResolvingDecoder in) throws java.io.IOException { + org.apache.avro.Schema.Field[] fieldOrder = in.readFieldOrderIfDiff(); + if (fieldOrder == null) { + this.street = in.readString(this.street instanceof Utf8 ? (Utf8) this.street : null); + + this.city = in.readString(this.city instanceof Utf8 ? (Utf8) this.city : null); + + } + else { + for (int i = 0; i < 2; i++) { + switch (fieldOrder[i].pos()) { + case 0: + this.street = in.readString(this.street instanceof Utf8 ? (Utf8) this.street : null); + break; + + case 1: + this.city = in.readString(this.city instanceof Utf8 ? (Utf8) this.city : null); + break; + + default: + throw new java.io.IOException("Corrupt ResolvingDecoder."); + } + } + } + } + +} diff --git a/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/avro/CustomerEvent.java b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/avro/CustomerEvent.java new file mode 100644 index 00000000..9e4cbc32 --- /dev/null +++ b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/avro/CustomerEvent.java @@ -0,0 +1,896 @@ +/** + * Autogenerated by Avro + * + * DO NOT EDIT DIRECTLY + */ +package org.springframework.modulith.events.scs.dtos.avro; + +import org.apache.avro.message.BinaryMessageDecoder; +import org.apache.avro.message.BinaryMessageEncoder; +import org.apache.avro.message.SchemaStore; +import org.apache.avro.specific.SpecificData; +import org.apache.avro.util.Utf8; + +@org.apache.avro.specific.AvroGenerated +public class CustomerEvent extends org.apache.avro.specific.SpecificRecordBase + implements org.apache.avro.specific.SpecificRecord { + + private static final long serialVersionUID = -8546638608361810155L; + + public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse( + "{\"type\":\"record\",\"name\":\"CustomerEvent\",\"namespace\":\"org.springframework.modulith.events.scs.dtos.avro\",\"fields\":[{\"name\":\"name\",\"type\":\"string\",\"doc\":\"Customer name\"},{\"name\":\"email\",\"type\":\"string\",\"doc\":\"\"},{\"name\":\"addresses\",\"type\":{\"type\":\"array\",\"items\":{\"type\":\"record\",\"name\":\"Address\",\"fields\":[{\"name\":\"street\",\"type\":\"string\"},{\"name\":\"city\",\"type\":\"string\"}]},\"java-class\":\"java.util.List\"}},{\"name\":\"id\",\"type\":[\"null\",\"long\"]},{\"name\":\"version\",\"type\":[\"null\",\"int\"]},{\"name\":\"paymentMethods\",\"type\":{\"type\":\"array\",\"items\":{\"type\":\"record\",\"name\":\"PaymentMethod\",\"fields\":[{\"name\":\"id\",\"type\":\"int\"},{\"name\":\"type\",\"type\":{\"type\":\"enum\",\"name\":\"PaymentMethodType\",\"symbols\":[\"VISA\",\"MASTERCARD\"]}},{\"name\":\"cardNumber\",\"type\":\"string\"}]},\"java-class\":\"java.util.List\"}}]}"); + + public static org.apache.avro.Schema getClassSchema() { + return SCHEMA$; + } + + private static final SpecificData MODEL$ = new SpecificData(); + + private static final BinaryMessageEncoder ENCODER = new BinaryMessageEncoder<>(MODEL$, SCHEMA$); + + private static final BinaryMessageDecoder DECODER = new BinaryMessageDecoder<>(MODEL$, SCHEMA$); + + /** + * Return the BinaryMessageEncoder instance used by this class. + * @return the message encoder used by this class + */ + public static BinaryMessageEncoder getEncoder() { + return ENCODER; + } + + /** + * Return the BinaryMessageDecoder instance used by this class. + * @return the message decoder used by this class + */ + public static BinaryMessageDecoder getDecoder() { + return DECODER; + } + + /** + * Create a new BinaryMessageDecoder instance for this class that uses the specified + * {@link SchemaStore}. + * @param resolver a {@link SchemaStore} used to find schemas by fingerprint + * @return a BinaryMessageDecoder instance for this class backed by the given + * SchemaStore + */ + public static BinaryMessageDecoder createDecoder(SchemaStore resolver) { + return new BinaryMessageDecoder<>(MODEL$, SCHEMA$, resolver); + } + + /** + * Serializes this CustomerEvent to a ByteBuffer. + * @return a buffer holding the serialized data for this instance + * @throws java.io.IOException if this instance could not be serialized + */ + public java.nio.ByteBuffer toByteBuffer() throws java.io.IOException { + return ENCODER.encode(this); + } + + /** + * Deserializes a CustomerEvent from a ByteBuffer. + * @param b a byte buffer holding serialized data for an instance of this class + * @return a CustomerEvent instance decoded from the given buffer + * @throws java.io.IOException if the given bytes could not be deserialized into an + * instance of this class + */ + public static CustomerEvent fromByteBuffer(java.nio.ByteBuffer b) throws java.io.IOException { + return DECODER.decode(b); + } + + /** Customer name */ + private CharSequence name; + + private CharSequence email; + + private java.util.List
addresses; + + private Long id; + + private Integer version; + + private java.util.List paymentMethods; + + /** + * Default constructor. Note that this does not initialize fields to their default + * values from the schema. If that is desired then one should use + * newBuilder(). + */ + public CustomerEvent() { + } + + /** + * All-args constructor. + * @param name Customer name + * @param email The new value for email + * @param addresses The new value for addresses + * @param id The new value for id + * @param version The new value for version + * @param paymentMethods The new value for paymentMethods + */ + public CustomerEvent(CharSequence name, CharSequence email, + java.util.List
addresses, Long id, Integer version, + java.util.List paymentMethods) { + this.name = name; + this.email = email; + this.addresses = addresses; + this.id = id; + this.version = version; + this.paymentMethods = paymentMethods; + } + + @Override + public SpecificData getSpecificData() { + return MODEL$; + } + + @Override + public org.apache.avro.Schema getSchema() { + return SCHEMA$; + } + + // Used by DatumWriter. Applications should not call. + @Override + public Object get(int field$) { + switch (field$) { + case 0: + return name; + case 1: + return email; + case 2: + return addresses; + case 3: + return id; + case 4: + return version; + case 5: + return paymentMethods; + default: + throw new IndexOutOfBoundsException("Invalid index: " + field$); + } + } + + // Used by DatumReader. Applications should not call. + @Override + @SuppressWarnings(value = "unchecked") + public void put(int field$, Object value$) { + switch (field$) { + case 0: + name = (CharSequence) value$; + break; + case 1: + email = (CharSequence) value$; + break; + case 2: + addresses = (java.util.List
) value$; + break; + case 3: + id = (Long) value$; + break; + case 4: + version = (Integer) value$; + break; + case 5: + paymentMethods = (java.util.List) value$; + break; + default: + throw new IndexOutOfBoundsException("Invalid index: " + field$); + } + } + + /** + * Gets the value of the 'name' field. + * @return Customer name + */ + public CharSequence getName() { + return name; + } + + /** + * Sets the value of the 'name' field. Customer name + * @param value the value to set. + */ + public void setName(CharSequence value) { + this.name = value; + } + + /** + * Gets the value of the 'email' field. + * @return The value of the 'email' field. + */ + public CharSequence getEmail() { + return email; + } + + /** + * Sets the value of the 'email' field. + * @param value the value to set. + */ + public void setEmail(CharSequence value) { + this.email = value; + } + + /** + * Gets the value of the 'addresses' field. + * @return The value of the 'addresses' field. + */ + public java.util.List
getAddresses() { + return addresses; + } + + /** + * Sets the value of the 'addresses' field. + * @param value the value to set. + */ + public void setAddresses(java.util.List
value) { + this.addresses = value; + } + + /** + * Gets the value of the 'id' field. + * @return The value of the 'id' field. + */ + public Long getId() { + return id; + } + + /** + * Sets the value of the 'id' field. + * @param value the value to set. + */ + public void setId(Long value) { + this.id = value; + } + + /** + * Gets the value of the 'version' field. + * @return The value of the 'version' field. + */ + public Integer getVersion() { + return version; + } + + /** + * Sets the value of the 'version' field. + * @param value the value to set. + */ + public void setVersion(Integer value) { + this.version = value; + } + + /** + * Gets the value of the 'paymentMethods' field. + * @return The value of the 'paymentMethods' field. + */ + public java.util.List getPaymentMethods() { + return paymentMethods; + } + + /** + * Sets the value of the 'paymentMethods' field. + * @param value the value to set. + */ + public void setPaymentMethods(java.util.List value) { + this.paymentMethods = value; + } + + /** + * Creates a new CustomerEvent RecordBuilder. + * @return A new CustomerEvent RecordBuilder + */ + public static CustomerEvent.Builder newBuilder() { + return new CustomerEvent.Builder(); + } + + /** + * Creates a new CustomerEvent RecordBuilder by copying an existing Builder. + * @param other The existing builder to copy. + * @return A new CustomerEvent RecordBuilder + */ + public static CustomerEvent.Builder newBuilder( + CustomerEvent.Builder other) { + if (other == null) { + return new CustomerEvent.Builder(); + } + else { + return new CustomerEvent.Builder(other); + } + } + + /** + * Creates a new CustomerEvent RecordBuilder by copying an existing CustomerEvent + * instance. + * @param other The existing instance to copy. + * @return A new CustomerEvent RecordBuilder + */ + public static CustomerEvent.Builder newBuilder( + CustomerEvent other) { + if (other == null) { + return new CustomerEvent.Builder(); + } + else { + return new CustomerEvent.Builder(other); + } + } + + /** + * RecordBuilder for CustomerEvent instances. + */ + @org.apache.avro.specific.AvroGenerated + public static class Builder extends org.apache.avro.specific.SpecificRecordBuilderBase + implements org.apache.avro.data.RecordBuilder { + + /** Customer name */ + private CharSequence name; + + private CharSequence email; + + private java.util.List
addresses; + + private Long id; + + private Integer version; + + private java.util.List paymentMethods; + + /** Creates a new Builder */ + private Builder() { + super(SCHEMA$, MODEL$); + } + + /** + * Creates a Builder by copying an existing Builder. + * @param other The existing Builder to copy. + */ + private Builder(CustomerEvent.Builder other) { + super(other); + if (isValidValue(fields()[0], other.name)) { + this.name = data().deepCopy(fields()[0].schema(), other.name); + fieldSetFlags()[0] = other.fieldSetFlags()[0]; + } + if (isValidValue(fields()[1], other.email)) { + this.email = data().deepCopy(fields()[1].schema(), other.email); + fieldSetFlags()[1] = other.fieldSetFlags()[1]; + } + if (isValidValue(fields()[2], other.addresses)) { + this.addresses = data().deepCopy(fields()[2].schema(), other.addresses); + fieldSetFlags()[2] = other.fieldSetFlags()[2]; + } + if (isValidValue(fields()[3], other.id)) { + this.id = data().deepCopy(fields()[3].schema(), other.id); + fieldSetFlags()[3] = other.fieldSetFlags()[3]; + } + if (isValidValue(fields()[4], other.version)) { + this.version = data().deepCopy(fields()[4].schema(), other.version); + fieldSetFlags()[4] = other.fieldSetFlags()[4]; + } + if (isValidValue(fields()[5], other.paymentMethods)) { + this.paymentMethods = data().deepCopy(fields()[5].schema(), other.paymentMethods); + fieldSetFlags()[5] = other.fieldSetFlags()[5]; + } + } + + /** + * Creates a Builder by copying an existing CustomerEvent instance + * @param other The existing instance to copy. + */ + private Builder(CustomerEvent other) { + super(SCHEMA$, MODEL$); + if (isValidValue(fields()[0], other.name)) { + this.name = data().deepCopy(fields()[0].schema(), other.name); + fieldSetFlags()[0] = true; + } + if (isValidValue(fields()[1], other.email)) { + this.email = data().deepCopy(fields()[1].schema(), other.email); + fieldSetFlags()[1] = true; + } + if (isValidValue(fields()[2], other.addresses)) { + this.addresses = data().deepCopy(fields()[2].schema(), other.addresses); + fieldSetFlags()[2] = true; + } + if (isValidValue(fields()[3], other.id)) { + this.id = data().deepCopy(fields()[3].schema(), other.id); + fieldSetFlags()[3] = true; + } + if (isValidValue(fields()[4], other.version)) { + this.version = data().deepCopy(fields()[4].schema(), other.version); + fieldSetFlags()[4] = true; + } + if (isValidValue(fields()[5], other.paymentMethods)) { + this.paymentMethods = data().deepCopy(fields()[5].schema(), other.paymentMethods); + fieldSetFlags()[5] = true; + } + } + + /** + * Gets the value of the 'name' field. Customer name + * @return The value. + */ + public CharSequence getName() { + return name; + } + + /** + * Sets the value of the 'name' field. Customer name + * @param value The value of 'name'. + * @return This builder. + */ + public CustomerEvent.Builder setName(CharSequence value) { + validate(fields()[0], value); + this.name = value; + fieldSetFlags()[0] = true; + return this; + } + + /** + * Checks whether the 'name' field has been set. Customer name + * @return True if the 'name' field has been set, false otherwise. + */ + public boolean hasName() { + return fieldSetFlags()[0]; + } + + /** + * Clears the value of the 'name' field. Customer name + * @return This builder. + */ + public CustomerEvent.Builder clearName() { + name = null; + fieldSetFlags()[0] = false; + return this; + } + + /** + * Gets the value of the 'email' field. + * @return The value. + */ + public CharSequence getEmail() { + return email; + } + + /** + * Sets the value of the 'email' field. + * @param value The value of 'email'. + * @return This builder. + */ + public CustomerEvent.Builder setEmail(CharSequence value) { + validate(fields()[1], value); + this.email = value; + fieldSetFlags()[1] = true; + return this; + } + + /** + * Checks whether the 'email' field has been set. + * @return True if the 'email' field has been set, false otherwise. + */ + public boolean hasEmail() { + return fieldSetFlags()[1]; + } + + /** + * Clears the value of the 'email' field. + * @return This builder. + */ + public CustomerEvent.Builder clearEmail() { + email = null; + fieldSetFlags()[1] = false; + return this; + } + + /** + * Gets the value of the 'addresses' field. + * @return The value. + */ + public java.util.List
getAddresses() { + return addresses; + } + + /** + * Sets the value of the 'addresses' field. + * @param value The value of 'addresses'. + * @return This builder. + */ + public CustomerEvent.Builder setAddresses( + java.util.List
value) { + validate(fields()[2], value); + this.addresses = value; + fieldSetFlags()[2] = true; + return this; + } + + /** + * Checks whether the 'addresses' field has been set. + * @return True if the 'addresses' field has been set, false otherwise. + */ + public boolean hasAddresses() { + return fieldSetFlags()[2]; + } + + /** + * Clears the value of the 'addresses' field. + * @return This builder. + */ + public CustomerEvent.Builder clearAddresses() { + addresses = null; + fieldSetFlags()[2] = false; + return this; + } + + /** + * Gets the value of the 'id' field. + * @return The value. + */ + public Long getId() { + return id; + } + + /** + * Sets the value of the 'id' field. + * @param value The value of 'id'. + * @return This builder. + */ + public CustomerEvent.Builder setId(Long value) { + validate(fields()[3], value); + this.id = value; + fieldSetFlags()[3] = true; + return this; + } + + /** + * Checks whether the 'id' field has been set. + * @return True if the 'id' field has been set, false otherwise. + */ + public boolean hasId() { + return fieldSetFlags()[3]; + } + + /** + * Clears the value of the 'id' field. + * @return This builder. + */ + public CustomerEvent.Builder clearId() { + id = null; + fieldSetFlags()[3] = false; + return this; + } + + /** + * Gets the value of the 'version' field. + * @return The value. + */ + public Integer getVersion() { + return version; + } + + /** + * Sets the value of the 'version' field. + * @param value The value of 'version'. + * @return This builder. + */ + public CustomerEvent.Builder setVersion(Integer value) { + validate(fields()[4], value); + this.version = value; + fieldSetFlags()[4] = true; + return this; + } + + /** + * Checks whether the 'version' field has been set. + * @return True if the 'version' field has been set, false otherwise. + */ + public boolean hasVersion() { + return fieldSetFlags()[4]; + } + + /** + * Clears the value of the 'version' field. + * @return This builder. + */ + public CustomerEvent.Builder clearVersion() { + version = null; + fieldSetFlags()[4] = false; + return this; + } + + /** + * Gets the value of the 'paymentMethods' field. + * @return The value. + */ + public java.util.List getPaymentMethods() { + return paymentMethods; + } + + /** + * Sets the value of the 'paymentMethods' field. + * @param value The value of 'paymentMethods'. + * @return This builder. + */ + public CustomerEvent.Builder setPaymentMethods( + java.util.List value) { + validate(fields()[5], value); + this.paymentMethods = value; + fieldSetFlags()[5] = true; + return this; + } + + /** + * Checks whether the 'paymentMethods' field has been set. + * @return True if the 'paymentMethods' field has been set, false otherwise. + */ + public boolean hasPaymentMethods() { + return fieldSetFlags()[5]; + } + + /** + * Clears the value of the 'paymentMethods' field. + * @return This builder. + */ + public CustomerEvent.Builder clearPaymentMethods() { + paymentMethods = null; + fieldSetFlags()[5] = false; + return this; + } + + @Override + @SuppressWarnings("unchecked") + public CustomerEvent build() { + try { + CustomerEvent record = new CustomerEvent(); + record.name = fieldSetFlags()[0] ? this.name : (CharSequence) defaultValue(fields()[0]); + record.email = fieldSetFlags()[1] ? this.email : (CharSequence) defaultValue(fields()[1]); + record.addresses = fieldSetFlags()[2] ? this.addresses + : (java.util.List
) defaultValue( + fields()[2]); + record.id = fieldSetFlags()[3] ? this.id : (Long) defaultValue(fields()[3]); + record.version = fieldSetFlags()[4] ? this.version : (Integer) defaultValue(fields()[4]); + record.paymentMethods = fieldSetFlags()[5] ? this.paymentMethods + : (java.util.List) defaultValue( + fields()[5]); + return record; + } + catch (org.apache.avro.AvroMissingFieldException e) { + throw e; + } + catch (Exception e) { + throw new org.apache.avro.AvroRuntimeException(e); + } + } + + } + + @SuppressWarnings("unchecked") + private static final org.apache.avro.io.DatumWriter WRITER$ = (org.apache.avro.io.DatumWriter) MODEL$ + .createDatumWriter(SCHEMA$); + + @Override + public void writeExternal(java.io.ObjectOutput out) throws java.io.IOException { + WRITER$.write(this, SpecificData.getEncoder(out)); + } + + @SuppressWarnings("unchecked") + private static final org.apache.avro.io.DatumReader READER$ = (org.apache.avro.io.DatumReader) MODEL$ + .createDatumReader(SCHEMA$); + + @Override + public void readExternal(java.io.ObjectInput in) throws java.io.IOException { + READER$.read(this, SpecificData.getDecoder(in)); + } + + @Override + protected boolean hasCustomCoders() { + return true; + } + + @Override + public void customEncode(org.apache.avro.io.Encoder out) throws java.io.IOException { + out.writeString(this.name); + + out.writeString(this.email); + + long size0 = this.addresses.size(); + out.writeArrayStart(); + out.setItemCount(size0); + long actualSize0 = 0; + for (Address e0 : this.addresses) { + actualSize0++; + out.startItem(); + e0.customEncode(out); + } + out.writeArrayEnd(); + if (actualSize0 != size0) + throw new java.util.ConcurrentModificationException( + "Array-size written was " + size0 + ", but element count was " + actualSize0 + "."); + + if (this.id == null) { + out.writeIndex(0); + out.writeNull(); + } + else { + out.writeIndex(1); + out.writeLong(this.id); + } + + if (this.version == null) { + out.writeIndex(0); + out.writeNull(); + } + else { + out.writeIndex(1); + out.writeInt(this.version); + } + + long size1 = this.paymentMethods.size(); + out.writeArrayStart(); + out.setItemCount(size1); + long actualSize1 = 0; + for (PaymentMethod e1 : this.paymentMethods) { + actualSize1++; + out.startItem(); + e1.customEncode(out); + } + out.writeArrayEnd(); + if (actualSize1 != size1) + throw new java.util.ConcurrentModificationException( + "Array-size written was " + size1 + ", but element count was " + actualSize1 + "."); + + } + + @Override + public void customDecode(org.apache.avro.io.ResolvingDecoder in) throws java.io.IOException { + org.apache.avro.Schema.Field[] fieldOrder = in.readFieldOrderIfDiff(); + if (fieldOrder == null) { + this.name = in.readString(this.name instanceof Utf8 ? (Utf8) this.name : null); + + this.email = in.readString(this.email instanceof Utf8 ? (Utf8) this.email : null); + + long size0 = in.readArrayStart(); + java.util.List
a0 = this.addresses; + if (a0 == null) { + a0 = new SpecificData.Array
((int) size0, + SCHEMA$.getField("addresses").schema()); + this.addresses = a0; + } + else + a0.clear(); + SpecificData.Array
ga0 = (a0 instanceof SpecificData.Array + ? (SpecificData.Array
) a0 : null); + for (; 0 < size0; size0 = in.arrayNext()) { + for (; size0 != 0; size0--) { + Address e0 = (ga0 != null ? ga0.peek() : null); + if (e0 == null) { + e0 = new Address(); + } + e0.customDecode(in); + a0.add(e0); + } + } + + if (in.readIndex() != 1) { + in.readNull(); + this.id = null; + } + else { + this.id = in.readLong(); + } + + if (in.readIndex() != 1) { + in.readNull(); + this.version = null; + } + else { + this.version = in.readInt(); + } + + long size1 = in.readArrayStart(); + java.util.List a1 = this.paymentMethods; + if (a1 == null) { + a1 = new SpecificData.Array((int) size1, + SCHEMA$.getField("paymentMethods").schema()); + this.paymentMethods = a1; + } + else + a1.clear(); + SpecificData.Array ga1 = (a1 instanceof SpecificData.Array + ? (SpecificData.Array) a1 : null); + for (; 0 < size1; size1 = in.arrayNext()) { + for (; size1 != 0; size1--) { + PaymentMethod e1 = (ga1 != null ? ga1.peek() : null); + if (e1 == null) { + e1 = new PaymentMethod(); + } + e1.customDecode(in); + a1.add(e1); + } + } + + } + else { + for (int i = 0; i < 6; i++) { + switch (fieldOrder[i].pos()) { + case 0: + this.name = in.readString(this.name instanceof Utf8 ? (Utf8) this.name : null); + break; + + case 1: + this.email = in.readString(this.email instanceof Utf8 ? (Utf8) this.email : null); + break; + + case 2: + long size0 = in.readArrayStart(); + java.util.List
a0 = this.addresses; + if (a0 == null) { + a0 = new SpecificData.Array
( + (int) size0, SCHEMA$.getField("addresses").schema()); + this.addresses = a0; + } + else + a0.clear(); + SpecificData.Array
ga0 = (a0 instanceof SpecificData.Array + ? (SpecificData.Array
) a0 : null); + for (; 0 < size0; size0 = in.arrayNext()) { + for (; size0 != 0; size0--) { + Address e0 = (ga0 != null ? ga0.peek() + : null); + if (e0 == null) { + e0 = new Address(); + } + e0.customDecode(in); + a0.add(e0); + } + } + break; + + case 3: + if (in.readIndex() != 1) { + in.readNull(); + this.id = null; + } + else { + this.id = in.readLong(); + } + break; + + case 4: + if (in.readIndex() != 1) { + in.readNull(); + this.version = null; + } + else { + this.version = in.readInt(); + } + break; + + case 5: + long size1 = in.readArrayStart(); + java.util.List a1 = this.paymentMethods; + if (a1 == null) { + a1 = new SpecificData.Array( + (int) size1, SCHEMA$.getField("paymentMethods").schema()); + this.paymentMethods = a1; + } + else + a1.clear(); + SpecificData.Array ga1 = (a1 instanceof SpecificData.Array + ? (SpecificData.Array) a1 + : null); + for (; 0 < size1; size1 = in.arrayNext()) { + for (; size1 != 0; size1--) { + PaymentMethod e1 = (ga1 != null ? ga1.peek() + : null); + if (e1 == null) { + e1 = new PaymentMethod(); + } + e1.customDecode(in); + a1.add(e1); + } + } + break; + + default: + throw new java.io.IOException("Corrupt ResolvingDecoder."); + } + } + } + } + +} diff --git a/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/avro/ExternalizedCustomerEvent.java b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/avro/ExternalizedCustomerEvent.java new file mode 100644 index 00000000..4c4e5d74 --- /dev/null +++ b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/avro/ExternalizedCustomerEvent.java @@ -0,0 +1,898 @@ +/** + * Autogenerated by Avro + * + * DO NOT EDIT DIRECTLY + */ +package org.springframework.modulith.events.scs.dtos.avro; + +import org.apache.avro.message.BinaryMessageDecoder; +import org.apache.avro.message.BinaryMessageEncoder; +import org.apache.avro.message.SchemaStore; +import org.apache.avro.specific.SpecificData; +import org.apache.avro.util.Utf8; +import org.springframework.modulith.events.Externalized; + +@Externalized("customers-avro-externalized-out-0::#{#this.getId()}") +@org.apache.avro.specific.AvroGenerated +public class ExternalizedCustomerEvent extends org.apache.avro.specific.SpecificRecordBase + implements org.apache.avro.specific.SpecificRecord { + + private static final long serialVersionUID = -8546638608361810155L; + + public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse( + "{\"type\":\"record\",\"name\":\"ExternalizedCustomerEvent\",\"namespace\":\"io.zenwave360.modulith.events.scs.dtos.avro\",\"fields\":[{\"name\":\"name\",\"type\":\"string\",\"doc\":\"Customer name\"},{\"name\":\"email\",\"type\":\"string\",\"doc\":\"\"},{\"name\":\"addresses\",\"type\":{\"type\":\"array\",\"items\":{\"type\":\"record\",\"name\":\"Address\",\"fields\":[{\"name\":\"street\",\"type\":\"string\"},{\"name\":\"city\",\"type\":\"string\"}]},\"java-class\":\"java.util.List\"}},{\"name\":\"id\",\"type\":[\"null\",\"long\"]},{\"name\":\"version\",\"type\":[\"null\",\"int\"]},{\"name\":\"paymentMethods\",\"type\":{\"type\":\"array\",\"items\":{\"type\":\"record\",\"name\":\"PaymentMethod\",\"fields\":[{\"name\":\"id\",\"type\":\"int\"},{\"name\":\"type\",\"type\":{\"type\":\"enum\",\"name\":\"PaymentMethodType\",\"symbols\":[\"VISA\",\"MASTERCARD\"]}},{\"name\":\"cardNumber\",\"type\":\"string\"}]},\"java-class\":\"java.util.List\"}}]}"); + + public static org.apache.avro.Schema getClassSchema() { + return SCHEMA$; + } + + private static final SpecificData MODEL$ = new SpecificData(); + + private static final BinaryMessageEncoder ENCODER = new BinaryMessageEncoder<>(MODEL$, SCHEMA$); + + private static final BinaryMessageDecoder DECODER = new BinaryMessageDecoder<>(MODEL$, SCHEMA$); + + /** + * Return the BinaryMessageEncoder instance used by this class. + * @return the message encoder used by this class + */ + public static BinaryMessageEncoder getEncoder() { + return ENCODER; + } + + /** + * Return the BinaryMessageDecoder instance used by this class. + * @return the message decoder used by this class + */ + public static BinaryMessageDecoder getDecoder() { + return DECODER; + } + + /** + * Create a new BinaryMessageDecoder instance for this class that uses the specified + * {@link SchemaStore}. + * @param resolver a {@link SchemaStore} used to find schemas by fingerprint + * @return a BinaryMessageDecoder instance for this class backed by the given + * SchemaStore + */ + public static BinaryMessageDecoder createDecoder(SchemaStore resolver) { + return new BinaryMessageDecoder<>(MODEL$, SCHEMA$, resolver); + } + + /** + * Serializes this CustomerEvent to a ByteBuffer. + * @return a buffer holding the serialized data for this instance + * @throws java.io.IOException if this instance could not be serialized + */ + public java.nio.ByteBuffer toByteBuffer() throws java.io.IOException { + return ENCODER.encode(this); + } + + /** + * Deserializes a CustomerEvent from a ByteBuffer. + * @param b a byte buffer holding serialized data for an instance of this class + * @return a CustomerEvent instance decoded from the given buffer + * @throws java.io.IOException if the given bytes could not be deserialized into an + * instance of this class + */ + public static ExternalizedCustomerEvent fromByteBuffer(java.nio.ByteBuffer b) throws java.io.IOException { + return DECODER.decode(b); + } + + /** Customer name */ + private CharSequence name; + + private CharSequence email; + + private java.util.List
addresses; + + private Long id; + + private Integer version; + + private java.util.List paymentMethods; + + /** + * Default constructor. Note that this does not initialize fields to their default + * values from the schema. If that is desired then one should use + * newBuilder(). + */ + public ExternalizedCustomerEvent() { + } + + /** + * All-args constructor. + * @param name Customer name + * @param email The new value for email + * @param addresses The new value for addresses + * @param id The new value for id + * @param version The new value for version + * @param paymentMethods The new value for paymentMethods + */ + public ExternalizedCustomerEvent(CharSequence name, CharSequence email, + java.util.List
addresses, Long id, Integer version, + java.util.List paymentMethods) { + this.name = name; + this.email = email; + this.addresses = addresses; + this.id = id; + this.version = version; + this.paymentMethods = paymentMethods; + } + + @Override + public SpecificData getSpecificData() { + return MODEL$; + } + + @Override + public org.apache.avro.Schema getSchema() { + return SCHEMA$; + } + + // Used by DatumWriter. Applications should not call. + @Override + public Object get(int field$) { + switch (field$) { + case 0: + return name; + case 1: + return email; + case 2: + return addresses; + case 3: + return id; + case 4: + return version; + case 5: + return paymentMethods; + default: + throw new IndexOutOfBoundsException("Invalid index: " + field$); + } + } + + // Used by DatumReader. Applications should not call. + @Override + @SuppressWarnings(value = "unchecked") + public void put(int field$, Object value$) { + switch (field$) { + case 0: + name = (CharSequence) value$; + break; + case 1: + email = (CharSequence) value$; + break; + case 2: + addresses = (java.util.List
) value$; + break; + case 3: + id = (Long) value$; + break; + case 4: + version = (Integer) value$; + break; + case 5: + paymentMethods = (java.util.List) value$; + break; + default: + throw new IndexOutOfBoundsException("Invalid index: " + field$); + } + } + + /** + * Gets the value of the 'name' field. + * @return Customer name + */ + public CharSequence getName() { + return name; + } + + /** + * Sets the value of the 'name' field. Customer name + * @param value the value to set. + */ + public void setName(CharSequence value) { + this.name = value; + } + + /** + * Gets the value of the 'email' field. + * @return The value of the 'email' field. + */ + public CharSequence getEmail() { + return email; + } + + /** + * Sets the value of the 'email' field. + * @param value the value to set. + */ + public void setEmail(CharSequence value) { + this.email = value; + } + + /** + * Gets the value of the 'addresses' field. + * @return The value of the 'addresses' field. + */ + public java.util.List
getAddresses() { + return addresses; + } + + /** + * Sets the value of the 'addresses' field. + * @param value the value to set. + */ + public void setAddresses(java.util.List
value) { + this.addresses = value; + } + + /** + * Gets the value of the 'id' field. + * @return The value of the 'id' field. + */ + public Long getId() { + return id; + } + + /** + * Sets the value of the 'id' field. + * @param value the value to set. + */ + public void setId(Long value) { + this.id = value; + } + + /** + * Gets the value of the 'version' field. + * @return The value of the 'version' field. + */ + public Integer getVersion() { + return version; + } + + /** + * Sets the value of the 'version' field. + * @param value the value to set. + */ + public void setVersion(Integer value) { + this.version = value; + } + + /** + * Gets the value of the 'paymentMethods' field. + * @return The value of the 'paymentMethods' field. + */ + public java.util.List getPaymentMethods() { + return paymentMethods; + } + + /** + * Sets the value of the 'paymentMethods' field. + * @param value the value to set. + */ + public void setPaymentMethods(java.util.List value) { + this.paymentMethods = value; + } + + /** + * Creates a new CustomerEvent RecordBuilder. + * @return A new CustomerEvent RecordBuilder + */ + public static ExternalizedCustomerEvent.Builder newBuilder() { + return new ExternalizedCustomerEvent.Builder(); + } + + /** + * Creates a new CustomerEvent RecordBuilder by copying an existing Builder. + * @param other The existing builder to copy. + * @return A new CustomerEvent RecordBuilder + */ + public static ExternalizedCustomerEvent.Builder newBuilder( + ExternalizedCustomerEvent.Builder other) { + if (other == null) { + return new ExternalizedCustomerEvent.Builder(); + } + else { + return new ExternalizedCustomerEvent.Builder(other); + } + } + + /** + * Creates a new CustomerEvent RecordBuilder by copying an existing CustomerEvent + * instance. + * @param other The existing instance to copy. + * @return A new CustomerEvent RecordBuilder + */ + public static ExternalizedCustomerEvent.Builder newBuilder( + ExternalizedCustomerEvent other) { + if (other == null) { + return new ExternalizedCustomerEvent.Builder(); + } + else { + return new ExternalizedCustomerEvent.Builder(other); + } + } + + /** + * RecordBuilder for CustomerEvent instances. + */ + @org.apache.avro.specific.AvroGenerated + public static class Builder extends org.apache.avro.specific.SpecificRecordBuilderBase + implements org.apache.avro.data.RecordBuilder { + + /** Customer name */ + private CharSequence name; + + private CharSequence email; + + private java.util.List
addresses; + + private Long id; + + private Integer version; + + private java.util.List paymentMethods; + + /** Creates a new Builder */ + private Builder() { + super(SCHEMA$, MODEL$); + } + + /** + * Creates a Builder by copying an existing Builder. + * @param other The existing Builder to copy. + */ + private Builder(ExternalizedCustomerEvent.Builder other) { + super(other); + if (isValidValue(fields()[0], other.name)) { + this.name = data().deepCopy(fields()[0].schema(), other.name); + fieldSetFlags()[0] = other.fieldSetFlags()[0]; + } + if (isValidValue(fields()[1], other.email)) { + this.email = data().deepCopy(fields()[1].schema(), other.email); + fieldSetFlags()[1] = other.fieldSetFlags()[1]; + } + if (isValidValue(fields()[2], other.addresses)) { + this.addresses = data().deepCopy(fields()[2].schema(), other.addresses); + fieldSetFlags()[2] = other.fieldSetFlags()[2]; + } + if (isValidValue(fields()[3], other.id)) { + this.id = data().deepCopy(fields()[3].schema(), other.id); + fieldSetFlags()[3] = other.fieldSetFlags()[3]; + } + if (isValidValue(fields()[4], other.version)) { + this.version = data().deepCopy(fields()[4].schema(), other.version); + fieldSetFlags()[4] = other.fieldSetFlags()[4]; + } + if (isValidValue(fields()[5], other.paymentMethods)) { + this.paymentMethods = data().deepCopy(fields()[5].schema(), other.paymentMethods); + fieldSetFlags()[5] = other.fieldSetFlags()[5]; + } + } + + /** + * Creates a Builder by copying an existing CustomerEvent instance + * @param other The existing instance to copy. + */ + private Builder(ExternalizedCustomerEvent other) { + super(SCHEMA$, MODEL$); + if (isValidValue(fields()[0], other.name)) { + this.name = data().deepCopy(fields()[0].schema(), other.name); + fieldSetFlags()[0] = true; + } + if (isValidValue(fields()[1], other.email)) { + this.email = data().deepCopy(fields()[1].schema(), other.email); + fieldSetFlags()[1] = true; + } + if (isValidValue(fields()[2], other.addresses)) { + this.addresses = data().deepCopy(fields()[2].schema(), other.addresses); + fieldSetFlags()[2] = true; + } + if (isValidValue(fields()[3], other.id)) { + this.id = data().deepCopy(fields()[3].schema(), other.id); + fieldSetFlags()[3] = true; + } + if (isValidValue(fields()[4], other.version)) { + this.version = data().deepCopy(fields()[4].schema(), other.version); + fieldSetFlags()[4] = true; + } + if (isValidValue(fields()[5], other.paymentMethods)) { + this.paymentMethods = data().deepCopy(fields()[5].schema(), other.paymentMethods); + fieldSetFlags()[5] = true; + } + } + + /** + * Gets the value of the 'name' field. Customer name + * @return The value. + */ + public CharSequence getName() { + return name; + } + + /** + * Sets the value of the 'name' field. Customer name + * @param value The value of 'name'. + * @return This builder. + */ + public ExternalizedCustomerEvent.Builder setName(CharSequence value) { + validate(fields()[0], value); + this.name = value; + fieldSetFlags()[0] = true; + return this; + } + + /** + * Checks whether the 'name' field has been set. Customer name + * @return True if the 'name' field has been set, false otherwise. + */ + public boolean hasName() { + return fieldSetFlags()[0]; + } + + /** + * Clears the value of the 'name' field. Customer name + * @return This builder. + */ + public ExternalizedCustomerEvent.Builder clearName() { + name = null; + fieldSetFlags()[0] = false; + return this; + } + + /** + * Gets the value of the 'email' field. + * @return The value. + */ + public CharSequence getEmail() { + return email; + } + + /** + * Sets the value of the 'email' field. + * @param value The value of 'email'. + * @return This builder. + */ + public ExternalizedCustomerEvent.Builder setEmail(CharSequence value) { + validate(fields()[1], value); + this.email = value; + fieldSetFlags()[1] = true; + return this; + } + + /** + * Checks whether the 'email' field has been set. + * @return True if the 'email' field has been set, false otherwise. + */ + public boolean hasEmail() { + return fieldSetFlags()[1]; + } + + /** + * Clears the value of the 'email' field. + * @return This builder. + */ + public ExternalizedCustomerEvent.Builder clearEmail() { + email = null; + fieldSetFlags()[1] = false; + return this; + } + + /** + * Gets the value of the 'addresses' field. + * @return The value. + */ + public java.util.List
getAddresses() { + return addresses; + } + + /** + * Sets the value of the 'addresses' field. + * @param value The value of 'addresses'. + * @return This builder. + */ + public ExternalizedCustomerEvent.Builder setAddresses( + java.util.List
value) { + validate(fields()[2], value); + this.addresses = value; + fieldSetFlags()[2] = true; + return this; + } + + /** + * Checks whether the 'addresses' field has been set. + * @return True if the 'addresses' field has been set, false otherwise. + */ + public boolean hasAddresses() { + return fieldSetFlags()[2]; + } + + /** + * Clears the value of the 'addresses' field. + * @return This builder. + */ + public ExternalizedCustomerEvent.Builder clearAddresses() { + addresses = null; + fieldSetFlags()[2] = false; + return this; + } + + /** + * Gets the value of the 'id' field. + * @return The value. + */ + public Long getId() { + return id; + } + + /** + * Sets the value of the 'id' field. + * @param value The value of 'id'. + * @return This builder. + */ + public ExternalizedCustomerEvent.Builder setId(Long value) { + validate(fields()[3], value); + this.id = value; + fieldSetFlags()[3] = true; + return this; + } + + /** + * Checks whether the 'id' field has been set. + * @return True if the 'id' field has been set, false otherwise. + */ + public boolean hasId() { + return fieldSetFlags()[3]; + } + + /** + * Clears the value of the 'id' field. + * @return This builder. + */ + public ExternalizedCustomerEvent.Builder clearId() { + id = null; + fieldSetFlags()[3] = false; + return this; + } + + /** + * Gets the value of the 'version' field. + * @return The value. + */ + public Integer getVersion() { + return version; + } + + /** + * Sets the value of the 'version' field. + * @param value The value of 'version'. + * @return This builder. + */ + public ExternalizedCustomerEvent.Builder setVersion(Integer value) { + validate(fields()[4], value); + this.version = value; + fieldSetFlags()[4] = true; + return this; + } + + /** + * Checks whether the 'version' field has been set. + * @return True if the 'version' field has been set, false otherwise. + */ + public boolean hasVersion() { + return fieldSetFlags()[4]; + } + + /** + * Clears the value of the 'version' field. + * @return This builder. + */ + public ExternalizedCustomerEvent.Builder clearVersion() { + version = null; + fieldSetFlags()[4] = false; + return this; + } + + /** + * Gets the value of the 'paymentMethods' field. + * @return The value. + */ + public java.util.List getPaymentMethods() { + return paymentMethods; + } + + /** + * Sets the value of the 'paymentMethods' field. + * @param value The value of 'paymentMethods'. + * @return This builder. + */ + public ExternalizedCustomerEvent.Builder setPaymentMethods( + java.util.List value) { + validate(fields()[5], value); + this.paymentMethods = value; + fieldSetFlags()[5] = true; + return this; + } + + /** + * Checks whether the 'paymentMethods' field has been set. + * @return True if the 'paymentMethods' field has been set, false otherwise. + */ + public boolean hasPaymentMethods() { + return fieldSetFlags()[5]; + } + + /** + * Clears the value of the 'paymentMethods' field. + * @return This builder. + */ + public ExternalizedCustomerEvent.Builder clearPaymentMethods() { + paymentMethods = null; + fieldSetFlags()[5] = false; + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ExternalizedCustomerEvent build() { + try { + ExternalizedCustomerEvent record = new ExternalizedCustomerEvent(); + record.name = fieldSetFlags()[0] ? this.name : (CharSequence) defaultValue(fields()[0]); + record.email = fieldSetFlags()[1] ? this.email : (CharSequence) defaultValue(fields()[1]); + record.addresses = fieldSetFlags()[2] ? this.addresses + : (java.util.List
) defaultValue( + fields()[2]); + record.id = fieldSetFlags()[3] ? this.id : (Long) defaultValue(fields()[3]); + record.version = fieldSetFlags()[4] ? this.version : (Integer) defaultValue(fields()[4]); + record.paymentMethods = fieldSetFlags()[5] ? this.paymentMethods + : (java.util.List) defaultValue( + fields()[5]); + return record; + } + catch (org.apache.avro.AvroMissingFieldException e) { + throw e; + } + catch (Exception e) { + throw new org.apache.avro.AvroRuntimeException(e); + } + } + + } + + @SuppressWarnings("unchecked") + private static final org.apache.avro.io.DatumWriter WRITER$ = (org.apache.avro.io.DatumWriter) MODEL$ + .createDatumWriter(SCHEMA$); + + @Override + public void writeExternal(java.io.ObjectOutput out) throws java.io.IOException { + WRITER$.write(this, SpecificData.getEncoder(out)); + } + + @SuppressWarnings("unchecked") + private static final org.apache.avro.io.DatumReader READER$ = (org.apache.avro.io.DatumReader) MODEL$ + .createDatumReader(SCHEMA$); + + @Override + public void readExternal(java.io.ObjectInput in) throws java.io.IOException { + READER$.read(this, SpecificData.getDecoder(in)); + } + + @Override + protected boolean hasCustomCoders() { + return true; + } + + @Override + public void customEncode(org.apache.avro.io.Encoder out) throws java.io.IOException { + out.writeString(this.name); + + out.writeString(this.email); + + long size0 = this.addresses.size(); + out.writeArrayStart(); + out.setItemCount(size0); + long actualSize0 = 0; + for (Address e0 : this.addresses) { + actualSize0++; + out.startItem(); + e0.customEncode(out); + } + out.writeArrayEnd(); + if (actualSize0 != size0) + throw new java.util.ConcurrentModificationException( + "Array-size written was " + size0 + ", but element count was " + actualSize0 + "."); + + if (this.id == null) { + out.writeIndex(0); + out.writeNull(); + } + else { + out.writeIndex(1); + out.writeLong(this.id); + } + + if (this.version == null) { + out.writeIndex(0); + out.writeNull(); + } + else { + out.writeIndex(1); + out.writeInt(this.version); + } + + long size1 = this.paymentMethods.size(); + out.writeArrayStart(); + out.setItemCount(size1); + long actualSize1 = 0; + for (PaymentMethod e1 : this.paymentMethods) { + actualSize1++; + out.startItem(); + e1.customEncode(out); + } + out.writeArrayEnd(); + if (actualSize1 != size1) + throw new java.util.ConcurrentModificationException( + "Array-size written was " + size1 + ", but element count was " + actualSize1 + "."); + + } + + @Override + public void customDecode(org.apache.avro.io.ResolvingDecoder in) throws java.io.IOException { + org.apache.avro.Schema.Field[] fieldOrder = in.readFieldOrderIfDiff(); + if (fieldOrder == null) { + this.name = in.readString(this.name instanceof Utf8 ? (Utf8) this.name : null); + + this.email = in.readString(this.email instanceof Utf8 ? (Utf8) this.email : null); + + long size0 = in.readArrayStart(); + java.util.List
a0 = this.addresses; + if (a0 == null) { + a0 = new SpecificData.Array
((int) size0, + SCHEMA$.getField("addresses").schema()); + this.addresses = a0; + } + else + a0.clear(); + SpecificData.Array
ga0 = (a0 instanceof SpecificData.Array + ? (SpecificData.Array
) a0 : null); + for (; 0 < size0; size0 = in.arrayNext()) { + for (; size0 != 0; size0--) { + Address e0 = (ga0 != null ? ga0.peek() : null); + if (e0 == null) { + e0 = new Address(); + } + e0.customDecode(in); + a0.add(e0); + } + } + + if (in.readIndex() != 1) { + in.readNull(); + this.id = null; + } + else { + this.id = in.readLong(); + } + + if (in.readIndex() != 1) { + in.readNull(); + this.version = null; + } + else { + this.version = in.readInt(); + } + + long size1 = in.readArrayStart(); + java.util.List a1 = this.paymentMethods; + if (a1 == null) { + a1 = new SpecificData.Array((int) size1, + SCHEMA$.getField("paymentMethods").schema()); + this.paymentMethods = a1; + } + else + a1.clear(); + SpecificData.Array ga1 = (a1 instanceof SpecificData.Array + ? (SpecificData.Array) a1 : null); + for (; 0 < size1; size1 = in.arrayNext()) { + for (; size1 != 0; size1--) { + PaymentMethod e1 = (ga1 != null ? ga1.peek() : null); + if (e1 == null) { + e1 = new PaymentMethod(); + } + e1.customDecode(in); + a1.add(e1); + } + } + + } + else { + for (int i = 0; i < 6; i++) { + switch (fieldOrder[i].pos()) { + case 0: + this.name = in.readString(this.name instanceof Utf8 ? (Utf8) this.name : null); + break; + + case 1: + this.email = in.readString(this.email instanceof Utf8 ? (Utf8) this.email : null); + break; + + case 2: + long size0 = in.readArrayStart(); + java.util.List
a0 = this.addresses; + if (a0 == null) { + a0 = new SpecificData.Array
( + (int) size0, SCHEMA$.getField("addresses").schema()); + this.addresses = a0; + } + else + a0.clear(); + SpecificData.Array
ga0 = (a0 instanceof SpecificData.Array + ? (SpecificData.Array
) a0 : null); + for (; 0 < size0; size0 = in.arrayNext()) { + for (; size0 != 0; size0--) { + Address e0 = (ga0 != null ? ga0.peek() + : null); + if (e0 == null) { + e0 = new Address(); + } + e0.customDecode(in); + a0.add(e0); + } + } + break; + + case 3: + if (in.readIndex() != 1) { + in.readNull(); + this.id = null; + } + else { + this.id = in.readLong(); + } + break; + + case 4: + if (in.readIndex() != 1) { + in.readNull(); + this.version = null; + } + else { + this.version = in.readInt(); + } + break; + + case 5: + long size1 = in.readArrayStart(); + java.util.List a1 = this.paymentMethods; + if (a1 == null) { + a1 = new SpecificData.Array( + (int) size1, SCHEMA$.getField("paymentMethods").schema()); + this.paymentMethods = a1; + } + else + a1.clear(); + SpecificData.Array ga1 = (a1 instanceof SpecificData.Array + ? (SpecificData.Array) a1 + : null); + for (; 0 < size1; size1 = in.arrayNext()) { + for (; size1 != 0; size1--) { + PaymentMethod e1 = (ga1 != null ? ga1.peek() + : null); + if (e1 == null) { + e1 = new PaymentMethod(); + } + e1.customDecode(in); + a1.add(e1); + } + } + break; + + default: + throw new java.io.IOException("Corrupt ResolvingDecoder."); + } + } + } + } + +} diff --git a/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/avro/PaymentMethod.java b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/avro/PaymentMethod.java new file mode 100644 index 00000000..09d9873a --- /dev/null +++ b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/avro/PaymentMethod.java @@ -0,0 +1,499 @@ +/** + * Autogenerated by Avro + * + * DO NOT EDIT DIRECTLY + */ +package org.springframework.modulith.events.scs.dtos.avro; + +import org.apache.avro.message.BinaryMessageDecoder; +import org.apache.avro.message.BinaryMessageEncoder; +import org.apache.avro.message.SchemaStore; +import org.apache.avro.specific.SpecificData; +import org.apache.avro.util.Utf8; + +@org.apache.avro.specific.AvroGenerated +public class PaymentMethod extends org.apache.avro.specific.SpecificRecordBase + implements org.apache.avro.specific.SpecificRecord { + + private static final long serialVersionUID = -3545422545868948468L; + + public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse( + "{\"type\":\"record\",\"name\":\"PaymentMethod\",\"namespace\":\"org.springframework.modulith.events.scs.dtos.avro\",\"fields\":[{\"name\":\"id\",\"type\":\"int\"},{\"name\":\"type\",\"type\":{\"type\":\"enum\",\"name\":\"PaymentMethodType\",\"symbols\":[\"VISA\",\"MASTERCARD\"]}},{\"name\":\"cardNumber\",\"type\":\"string\"}]}"); + + public static org.apache.avro.Schema getClassSchema() { + return SCHEMA$; + } + + private static final SpecificData MODEL$ = new SpecificData(); + + private static final BinaryMessageEncoder ENCODER = new BinaryMessageEncoder<>(MODEL$, SCHEMA$); + + private static final BinaryMessageDecoder DECODER = new BinaryMessageDecoder<>(MODEL$, SCHEMA$); + + /** + * Return the BinaryMessageEncoder instance used by this class. + * @return the message encoder used by this class + */ + public static BinaryMessageEncoder getEncoder() { + return ENCODER; + } + + /** + * Return the BinaryMessageDecoder instance used by this class. + * @return the message decoder used by this class + */ + public static BinaryMessageDecoder getDecoder() { + return DECODER; + } + + /** + * Create a new BinaryMessageDecoder instance for this class that uses the specified + * {@link SchemaStore}. + * @param resolver a {@link SchemaStore} used to find schemas by fingerprint + * @return a BinaryMessageDecoder instance for this class backed by the given + * SchemaStore + */ + public static BinaryMessageDecoder createDecoder(SchemaStore resolver) { + return new BinaryMessageDecoder<>(MODEL$, SCHEMA$, resolver); + } + + /** + * Serializes this PaymentMethod to a ByteBuffer. + * @return a buffer holding the serialized data for this instance + * @throws java.io.IOException if this instance could not be serialized + */ + public java.nio.ByteBuffer toByteBuffer() throws java.io.IOException { + return ENCODER.encode(this); + } + + /** + * Deserializes a PaymentMethod from a ByteBuffer. + * @param b a byte buffer holding serialized data for an instance of this class + * @return a PaymentMethod instance decoded from the given buffer + * @throws java.io.IOException if the given bytes could not be deserialized into an + * instance of this class + */ + public static PaymentMethod fromByteBuffer(java.nio.ByteBuffer b) throws java.io.IOException { + return DECODER.decode(b); + } + + private int id; + + private PaymentMethodType type; + + private CharSequence cardNumber; + + /** + * Default constructor. Note that this does not initialize fields to their default + * values from the schema. If that is desired then one should use + * newBuilder(). + */ + public PaymentMethod() { + } + + /** + * All-args constructor. + * @param id The new value for id + * @param type The new value for type + * @param cardNumber The new value for cardNumber + */ + public PaymentMethod(Integer id, PaymentMethodType type, + CharSequence cardNumber) { + this.id = id; + this.type = type; + this.cardNumber = cardNumber; + } + + @Override + public SpecificData getSpecificData() { + return MODEL$; + } + + @Override + public org.apache.avro.Schema getSchema() { + return SCHEMA$; + } + + // Used by DatumWriter. Applications should not call. + @Override + public Object get(int field$) { + switch (field$) { + case 0: + return id; + case 1: + return type; + case 2: + return cardNumber; + default: + throw new IndexOutOfBoundsException("Invalid index: " + field$); + } + } + + // Used by DatumReader. Applications should not call. + @Override + @SuppressWarnings(value = "unchecked") + public void put(int field$, Object value$) { + switch (field$) { + case 0: + id = (Integer) value$; + break; + case 1: + type = (PaymentMethodType) value$; + break; + case 2: + cardNumber = (CharSequence) value$; + break; + default: + throw new IndexOutOfBoundsException("Invalid index: " + field$); + } + } + + /** + * Gets the value of the 'id' field. + * @return The value of the 'id' field. + */ + public int getId() { + return id; + } + + /** + * Sets the value of the 'id' field. + * @param value the value to set. + */ + public void setId(int value) { + this.id = value; + } + + /** + * Gets the value of the 'type' field. + * @return The value of the 'type' field. + */ + public PaymentMethodType getType() { + return type; + } + + /** + * Sets the value of the 'type' field. + * @param value the value to set. + */ + public void setType(PaymentMethodType value) { + this.type = value; + } + + /** + * Gets the value of the 'cardNumber' field. + * @return The value of the 'cardNumber' field. + */ + public CharSequence getCardNumber() { + return cardNumber; + } + + /** + * Sets the value of the 'cardNumber' field. + * @param value the value to set. + */ + public void setCardNumber(CharSequence value) { + this.cardNumber = value; + } + + /** + * Creates a new PaymentMethod RecordBuilder. + * @return A new PaymentMethod RecordBuilder + */ + public static PaymentMethod.Builder newBuilder() { + return new PaymentMethod.Builder(); + } + + /** + * Creates a new PaymentMethod RecordBuilder by copying an existing Builder. + * @param other The existing builder to copy. + * @return A new PaymentMethod RecordBuilder + */ + public static PaymentMethod.Builder newBuilder( + PaymentMethod.Builder other) { + if (other == null) { + return new PaymentMethod.Builder(); + } + else { + return new PaymentMethod.Builder(other); + } + } + + /** + * Creates a new PaymentMethod RecordBuilder by copying an existing PaymentMethod + * instance. + * @param other The existing instance to copy. + * @return A new PaymentMethod RecordBuilder + */ + public static PaymentMethod.Builder newBuilder( + PaymentMethod other) { + if (other == null) { + return new PaymentMethod.Builder(); + } + else { + return new PaymentMethod.Builder(other); + } + } + + /** + * RecordBuilder for PaymentMethod instances. + */ + @org.apache.avro.specific.AvroGenerated + public static class Builder extends org.apache.avro.specific.SpecificRecordBuilderBase + implements org.apache.avro.data.RecordBuilder { + + private int id; + + private PaymentMethodType type; + + private CharSequence cardNumber; + + /** Creates a new Builder */ + private Builder() { + super(SCHEMA$, MODEL$); + } + + /** + * Creates a Builder by copying an existing Builder. + * @param other The existing Builder to copy. + */ + private Builder(PaymentMethod.Builder other) { + super(other); + if (isValidValue(fields()[0], other.id)) { + this.id = data().deepCopy(fields()[0].schema(), other.id); + fieldSetFlags()[0] = other.fieldSetFlags()[0]; + } + if (isValidValue(fields()[1], other.type)) { + this.type = data().deepCopy(fields()[1].schema(), other.type); + fieldSetFlags()[1] = other.fieldSetFlags()[1]; + } + if (isValidValue(fields()[2], other.cardNumber)) { + this.cardNumber = data().deepCopy(fields()[2].schema(), other.cardNumber); + fieldSetFlags()[2] = other.fieldSetFlags()[2]; + } + } + + /** + * Creates a Builder by copying an existing PaymentMethod instance + * @param other The existing instance to copy. + */ + private Builder(PaymentMethod other) { + super(SCHEMA$, MODEL$); + if (isValidValue(fields()[0], other.id)) { + this.id = data().deepCopy(fields()[0].schema(), other.id); + fieldSetFlags()[0] = true; + } + if (isValidValue(fields()[1], other.type)) { + this.type = data().deepCopy(fields()[1].schema(), other.type); + fieldSetFlags()[1] = true; + } + if (isValidValue(fields()[2], other.cardNumber)) { + this.cardNumber = data().deepCopy(fields()[2].schema(), other.cardNumber); + fieldSetFlags()[2] = true; + } + } + + /** + * Gets the value of the 'id' field. + * @return The value. + */ + public int getId() { + return id; + } + + /** + * Sets the value of the 'id' field. + * @param value The value of 'id'. + * @return This builder. + */ + public PaymentMethod.Builder setId(int value) { + validate(fields()[0], value); + this.id = value; + fieldSetFlags()[0] = true; + return this; + } + + /** + * Checks whether the 'id' field has been set. + * @return True if the 'id' field has been set, false otherwise. + */ + public boolean hasId() { + return fieldSetFlags()[0]; + } + + /** + * Clears the value of the 'id' field. + * @return This builder. + */ + public PaymentMethod.Builder clearId() { + fieldSetFlags()[0] = false; + return this; + } + + /** + * Gets the value of the 'type' field. + * @return The value. + */ + public PaymentMethodType getType() { + return type; + } + + /** + * Sets the value of the 'type' field. + * @param value The value of 'type'. + * @return This builder. + */ + public PaymentMethod.Builder setType( + PaymentMethodType value) { + validate(fields()[1], value); + this.type = value; + fieldSetFlags()[1] = true; + return this; + } + + /** + * Checks whether the 'type' field has been set. + * @return True if the 'type' field has been set, false otherwise. + */ + public boolean hasType() { + return fieldSetFlags()[1]; + } + + /** + * Clears the value of the 'type' field. + * @return This builder. + */ + public PaymentMethod.Builder clearType() { + type = null; + fieldSetFlags()[1] = false; + return this; + } + + /** + * Gets the value of the 'cardNumber' field. + * @return The value. + */ + public CharSequence getCardNumber() { + return cardNumber; + } + + /** + * Sets the value of the 'cardNumber' field. + * @param value The value of 'cardNumber'. + * @return This builder. + */ + public PaymentMethod.Builder setCardNumber(CharSequence value) { + validate(fields()[2], value); + this.cardNumber = value; + fieldSetFlags()[2] = true; + return this; + } + + /** + * Checks whether the 'cardNumber' field has been set. + * @return True if the 'cardNumber' field has been set, false otherwise. + */ + public boolean hasCardNumber() { + return fieldSetFlags()[2]; + } + + /** + * Clears the value of the 'cardNumber' field. + * @return This builder. + */ + public PaymentMethod.Builder clearCardNumber() { + cardNumber = null; + fieldSetFlags()[2] = false; + return this; + } + + @Override + @SuppressWarnings("unchecked") + public PaymentMethod build() { + try { + PaymentMethod record = new PaymentMethod(); + record.id = fieldSetFlags()[0] ? this.id : (Integer) defaultValue(fields()[0]); + record.type = fieldSetFlags()[1] ? this.type + : (PaymentMethodType) defaultValue(fields()[1]); + record.cardNumber = fieldSetFlags()[2] ? this.cardNumber : (CharSequence) defaultValue(fields()[2]); + return record; + } + catch (org.apache.avro.AvroMissingFieldException e) { + throw e; + } + catch (Exception e) { + throw new org.apache.avro.AvroRuntimeException(e); + } + } + + } + + @SuppressWarnings("unchecked") + private static final org.apache.avro.io.DatumWriter WRITER$ = (org.apache.avro.io.DatumWriter) MODEL$ + .createDatumWriter(SCHEMA$); + + @Override + public void writeExternal(java.io.ObjectOutput out) throws java.io.IOException { + WRITER$.write(this, SpecificData.getEncoder(out)); + } + + @SuppressWarnings("unchecked") + private static final org.apache.avro.io.DatumReader READER$ = (org.apache.avro.io.DatumReader) MODEL$ + .createDatumReader(SCHEMA$); + + @Override + public void readExternal(java.io.ObjectInput in) throws java.io.IOException { + READER$.read(this, SpecificData.getDecoder(in)); + } + + @Override + protected boolean hasCustomCoders() { + return true; + } + + @Override + public void customEncode(org.apache.avro.io.Encoder out) throws java.io.IOException { + out.writeInt(this.id); + + out.writeEnum(this.type.ordinal()); + + out.writeString(this.cardNumber); + + } + + @Override + public void customDecode(org.apache.avro.io.ResolvingDecoder in) throws java.io.IOException { + org.apache.avro.Schema.Field[] fieldOrder = in.readFieldOrderIfDiff(); + if (fieldOrder == null) { + this.id = in.readInt(); + + this.type = PaymentMethodType.values()[in.readEnum()]; + + this.cardNumber = in.readString(this.cardNumber instanceof Utf8 ? (Utf8) this.cardNumber : null); + + } + else { + for (int i = 0; i < 3; i++) { + switch (fieldOrder[i].pos()) { + case 0: + this.id = in.readInt(); + break; + + case 1: + this.type = PaymentMethodType.values()[in + .readEnum()]; + break; + + case 2: + this.cardNumber = in + .readString(this.cardNumber instanceof Utf8 ? (Utf8) this.cardNumber : null); + break; + + default: + throw new java.io.IOException("Corrupt ResolvingDecoder."); + } + } + } + } + +} diff --git a/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/avro/PaymentMethodType.java b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/avro/PaymentMethodType.java new file mode 100644 index 00000000..5294ee94 --- /dev/null +++ b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/avro/PaymentMethodType.java @@ -0,0 +1,25 @@ +/** + * Autogenerated by Avro + * + * DO NOT EDIT DIRECTLY + */ +package org.springframework.modulith.events.scs.dtos.avro; + +@org.apache.avro.specific.AvroGenerated +public enum PaymentMethodType implements org.apache.avro.generic.GenericEnumSymbol { + + VISA, MASTERCARD; + + public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse( + "{\"type\":\"enum\",\"name\":\"PaymentMethodType\",\"namespace\":\"org.springframework.modulith.events.scs.dtos.avro\",\"symbols\":[\"VISA\",\"MASTERCARD\"]}"); + + public static org.apache.avro.Schema getClassSchema() { + return SCHEMA$; + } + + @Override + public org.apache.avro.Schema getSchema() { + return SCHEMA$; + } + +} diff --git a/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/json/Address.java b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/json/Address.java new file mode 100644 index 00000000..e94c99db --- /dev/null +++ b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/json/Address.java @@ -0,0 +1,228 @@ + +package org.springframework.modulith.events.scs.dtos.json; + +import com.fasterxml.jackson.annotation.*; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +import java.io.Serializable; +import java.util.LinkedHashMap; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ "street", "city" }) +public class Address implements Serializable { + + /** + * + * (Required) + * + */ + @JsonProperty("street") + @Size(max = 254) + @NotNull + private String street; + + /** + * + * (Required) + * + */ + @JsonProperty("city") + @Size(max = 254) + @NotNull + private String city; + + @JsonIgnore + @Valid + private Map additionalProperties = new LinkedHashMap(); + + protected final static Object NOT_FOUND_VALUE = new Object(); + + private final static long serialVersionUID = -2054487487628445756L; + + /** + * + * (Required) + * + */ + @JsonProperty("street") + public String getStreet() { + return street; + } + + /** + * + * (Required) + * + */ + @JsonProperty("street") + public void setStreet(String street) { + this.street = street; + } + + public Address withStreet(String street) { + this.street = street; + return this; + } + + /** + * + * (Required) + * + */ + @JsonProperty("city") + public String getCity() { + return city; + } + + /** + * + * (Required) + * + */ + @JsonProperty("city") + public void setCity(String city) { + this.city = city; + } + + public Address withCity(String city) { + this.city = city; + return this; + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @JsonAnySetter + public void setAdditionalProperty(String name, Object value) { + this.additionalProperties.put(name, value); + } + + public Address withAdditionalProperty(String name, Object value) { + this.additionalProperties.put(name, value); + return this; + } + + protected boolean declaredProperty(String name, Object value) { + if ("street".equals(name)) { + if (value instanceof String) { + setStreet(((String) value)); + } + else { + throw new IllegalArgumentException(("property \"street\" is of type \"java.lang.String\", but got " + + value.getClass().toString())); + } + return true; + } + else { + if ("city".equals(name)) { + if (value instanceof String) { + setCity(((String) value)); + } + else { + throw new IllegalArgumentException(("property \"city\" is of type \"java.lang.String\", but got " + + value.getClass().toString())); + } + return true; + } + else { + return false; + } + } + } + + protected Object declaredPropertyOrNotFound(String name, Object notFoundValue) { + if ("street".equals(name)) { + return getStreet(); + } + else { + if ("city".equals(name)) { + return getCity(); + } + else { + return notFoundValue; + } + } + } + + @SuppressWarnings({ "unchecked" }) + public T get(String name) { + Object value = declaredPropertyOrNotFound(name, Address.NOT_FOUND_VALUE); + if (Address.NOT_FOUND_VALUE != value) { + return ((T) value); + } + else { + return ((T) getAdditionalProperties().get(name)); + } + } + + public void set(String name, Object value) { + if (!declaredProperty(name, value)) { + getAdditionalProperties().put(name, ((Object) value)); + } + } + + public Address with(String name, Object value) { + if (!declaredProperty(name, value)) { + getAdditionalProperties().put(name, ((Object) value)); + } + return this; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(Address.class.getName()) + .append('@') + .append(Integer.toHexString(System.identityHashCode(this))) + .append('['); + sb.append("street"); + sb.append('='); + sb.append(((this.street == null) ? "" : this.street)); + sb.append(','); + sb.append("city"); + sb.append('='); + sb.append(((this.city == null) ? "" : this.city)); + sb.append(','); + sb.append("additionalProperties"); + sb.append('='); + sb.append(((this.additionalProperties == null) ? "" : this.additionalProperties)); + sb.append(','); + if (sb.charAt((sb.length() - 1)) == ',') { + sb.setCharAt((sb.length() - 1), ']'); + } + else { + sb.append(']'); + } + return sb.toString(); + } + + @Override + public int hashCode() { + int result = 1; + result = ((result * 31) + ((this.additionalProperties == null) ? 0 : this.additionalProperties.hashCode())); + result = ((result * 31) + ((this.city == null) ? 0 : this.city.hashCode())); + result = ((result * 31) + ((this.street == null) ? 0 : this.street.hashCode())); + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof Address) == false) { + return false; + } + Address rhs = ((Address) other); + return ((((this.additionalProperties == rhs.additionalProperties) + || ((this.additionalProperties != null) && this.additionalProperties.equals(rhs.additionalProperties))) + && ((this.city == rhs.city) || ((this.city != null) && this.city.equals(rhs.city)))) + && ((this.street == rhs.street) || ((this.street != null) && this.street.equals(rhs.street)))); + } + +} diff --git a/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/json/CustomerEvent.java b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/json/CustomerEvent.java new file mode 100644 index 00000000..b220dcef --- /dev/null +++ b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/json/CustomerEvent.java @@ -0,0 +1,402 @@ + +package org.springframework.modulith.events.scs.dtos.json; + +import com.fasterxml.jackson.annotation.*; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ "name", "email", "addresses", "id", "version", "paymentMethods" }) +public class CustomerEvent implements Serializable { + + /** + * Customer name (Required) + * + */ + @JsonProperty("name") + @JsonPropertyDescription("Customer name") + @Size(max = 254) + @NotNull + private String name; + + /** + * + * (Required) + * + */ + @JsonProperty("email") + @Pattern(regexp = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}") + @Size(max = 254) + @NotNull + private String email; + + @JsonProperty("addresses") + @Valid + private List
addresses = new ArrayList
(); + + @JsonProperty("id") + private Long id; + + @JsonProperty("version") + private Long version; + + @JsonProperty("paymentMethods") + @Valid + private List paymentMethods = new ArrayList(); + + @JsonIgnore + @Valid + private Map additionalProperties = new LinkedHashMap(); + + protected final static Object NOT_FOUND_VALUE = new Object(); + + private final static long serialVersionUID = 2415813790372213850L; + + /** + * Customer name (Required) + * + */ + @JsonProperty("name") + public String getName() { + return name; + } + + /** + * Customer name (Required) + * + */ + @JsonProperty("name") + public void setName(String name) { + this.name = name; + } + + public CustomerEvent withName(String name) { + this.name = name; + return this; + } + + /** + * + * (Required) + * + */ + @JsonProperty("email") + public String getEmail() { + return email; + } + + /** + * + * (Required) + * + */ + @JsonProperty("email") + public void setEmail(String email) { + this.email = email; + } + + public CustomerEvent withEmail(String email) { + this.email = email; + return this; + } + + @JsonProperty("addresses") + public List
getAddresses() { + return addresses; + } + + @JsonProperty("addresses") + public void setAddresses(List
addresses) { + this.addresses = addresses; + } + + public CustomerEvent withAddresses(List
addresses) { + this.addresses = addresses; + return this; + } + + @JsonProperty("id") + public Long getId() { + return id; + } + + @JsonProperty("id") + public void setId(Long id) { + this.id = id; + } + + public CustomerEvent withId(Long id) { + this.id = id; + return this; + } + + @JsonProperty("version") + public Long getVersion() { + return version; + } + + @JsonProperty("version") + public void setVersion(Long version) { + this.version = version; + } + + public CustomerEvent withVersion(Long version) { + this.version = version; + return this; + } + + @JsonProperty("paymentMethods") + public List getPaymentMethods() { + return paymentMethods; + } + + @JsonProperty("paymentMethods") + public void setPaymentMethods(List paymentMethods) { + this.paymentMethods = paymentMethods; + } + + public CustomerEvent withPaymentMethods(List paymentMethods) { + this.paymentMethods = paymentMethods; + return this; + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @JsonAnySetter + public void setAdditionalProperty(String name, Object value) { + this.additionalProperties.put(name, value); + } + + public CustomerEvent withAdditionalProperty(String name, Object value) { + this.additionalProperties.put(name, value); + return this; + } + + protected boolean declaredProperty(String name, Object value) { + if ("name".equals(name)) { + if (value instanceof String) { + setName(((String) value)); + } + else { + throw new IllegalArgumentException( + ("property \"name\" is of type \"java.lang.String\", but got " + value.getClass().toString())); + } + return true; + } + else { + if ("email".equals(name)) { + if (value instanceof String) { + setEmail(((String) value)); + } + else { + throw new IllegalArgumentException(("property \"email\" is of type \"java.lang.String\", but got " + + value.getClass().toString())); + } + return true; + } + else { + if ("addresses".equals(name)) { + if (value instanceof List) { + setAddresses(((List
) value)); + } + else { + throw new IllegalArgumentException( + ("property \"addresses\" is of type \"java.util.List\", but got " + + value.getClass().toString())); + } + return true; + } + else { + if ("id".equals(name)) { + if (value instanceof Long) { + setId(((Long) value)); + } + else { + throw new IllegalArgumentException( + ("property \"id\" is of type \"java.lang.Long\", but got " + + value.getClass().toString())); + } + return true; + } + else { + if ("version".equals(name)) { + if (value instanceof Long) { + setVersion(((Long) value)); + } + else { + throw new IllegalArgumentException( + ("property \"version\" is of type \"java.lang.Long\", but got " + + value.getClass().toString())); + } + return true; + } + else { + if ("paymentMethods".equals(name)) { + if (value instanceof List) { + setPaymentMethods(((List) value)); + } + else { + throw new IllegalArgumentException( + ("property \"paymentMethods\" is of type \"java.util.List\", but got " + + value.getClass().toString())); + } + return true; + } + else { + return false; + } + } + } + } + } + } + } + + protected Object declaredPropertyOrNotFound(String name, Object notFoundValue) { + if ("name".equals(name)) { + return getName(); + } + else { + if ("email".equals(name)) { + return getEmail(); + } + else { + if ("addresses".equals(name)) { + return getAddresses(); + } + else { + if ("id".equals(name)) { + return getId(); + } + else { + if ("version".equals(name)) { + return getVersion(); + } + else { + if ("paymentMethods".equals(name)) { + return getPaymentMethods(); + } + else { + return notFoundValue; + } + } + } + } + } + } + } + + @SuppressWarnings({ "unchecked" }) + public T get(String name) { + Object value = declaredPropertyOrNotFound(name, CustomerEvent.NOT_FOUND_VALUE); + if (CustomerEvent.NOT_FOUND_VALUE != value) { + return ((T) value); + } + else { + return ((T) getAdditionalProperties().get(name)); + } + } + + public void set(String name, Object value) { + if (!declaredProperty(name, value)) { + getAdditionalProperties().put(name, ((Object) value)); + } + } + + public CustomerEvent with(String name, Object value) { + if (!declaredProperty(name, value)) { + getAdditionalProperties().put(name, ((Object) value)); + } + return this; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(CustomerEvent.class.getName()) + .append('@') + .append(Integer.toHexString(System.identityHashCode(this))) + .append('['); + sb.append("name"); + sb.append('='); + sb.append(((this.name == null) ? "" : this.name)); + sb.append(','); + sb.append("email"); + sb.append('='); + sb.append(((this.email == null) ? "" : this.email)); + sb.append(','); + sb.append("addresses"); + sb.append('='); + sb.append(((this.addresses == null) ? "" : this.addresses)); + sb.append(','); + sb.append("id"); + sb.append('='); + sb.append(((this.id == null) ? "" : this.id)); + sb.append(','); + sb.append("version"); + sb.append('='); + sb.append(((this.version == null) ? "" : this.version)); + sb.append(','); + sb.append("paymentMethods"); + sb.append('='); + sb.append(((this.paymentMethods == null) ? "" : this.paymentMethods)); + sb.append(','); + sb.append("additionalProperties"); + sb.append('='); + sb.append(((this.additionalProperties == null) ? "" : this.additionalProperties)); + sb.append(','); + if (sb.charAt((sb.length() - 1)) == ',') { + sb.setCharAt((sb.length() - 1), ']'); + } + else { + sb.append(']'); + } + return sb.toString(); + } + + @Override + public int hashCode() { + int result = 1; + result = ((result * 31) + ((this.addresses == null) ? 0 : this.addresses.hashCode())); + result = ((result * 31) + ((this.paymentMethods == null) ? 0 : this.paymentMethods.hashCode())); + result = ((result * 31) + ((this.name == null) ? 0 : this.name.hashCode())); + result = ((result * 31) + ((this.id == null) ? 0 : this.id.hashCode())); + result = ((result * 31) + ((this.additionalProperties == null) ? 0 : this.additionalProperties.hashCode())); + result = ((result * 31) + ((this.version == null) ? 0 : this.version.hashCode())); + result = ((result * 31) + ((this.email == null) ? 0 : this.email.hashCode())); + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof CustomerEvent) == false) { + return false; + } + CustomerEvent rhs = ((CustomerEvent) other); + return ((((((((this.addresses == rhs.addresses) + || ((this.addresses != null) && this.addresses.equals(rhs.addresses))) + && ((this.paymentMethods == rhs.paymentMethods) + || ((this.paymentMethods != null) && this.paymentMethods.equals(rhs.paymentMethods)))) + && ((this.name == rhs.name) || ((this.name != null) && this.name.equals(rhs.name)))) + && ((this.id == rhs.id) || ((this.id != null) && this.id.equals(rhs.id)))) + && ((this.additionalProperties == rhs.additionalProperties) || ((this.additionalProperties != null) + && this.additionalProperties.equals(rhs.additionalProperties)))) + && ((this.version == rhs.version) || ((this.version != null) && this.version.equals(rhs.version)))) + && ((this.email == rhs.email) || ((this.email != null) && this.email.equals(rhs.email)))); + } + +} diff --git a/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/json/ExternalizedCustomerEvent.java b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/json/ExternalizedCustomerEvent.java new file mode 100644 index 00000000..6742d198 --- /dev/null +++ b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/json/ExternalizedCustomerEvent.java @@ -0,0 +1,8 @@ +package org.springframework.modulith.events.scs.dtos.json; + +import org.springframework.modulith.events.Externalized; + +@Externalized("customers-json-externalized-out-0::#{#this.getName()}") +public class ExternalizedCustomerEvent extends CustomerEvent { + +} diff --git a/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/json/PaymentMethod.java b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/json/PaymentMethod.java new file mode 100644 index 00000000..f7d1dd51 --- /dev/null +++ b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/json/PaymentMethod.java @@ -0,0 +1,269 @@ + +package org.springframework.modulith.events.scs.dtos.json; + +import com.fasterxml.jackson.annotation.*; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; + +import java.io.Serializable; +import java.util.LinkedHashMap; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ "id", "type", "cardNumber" }) +public class PaymentMethod implements Serializable { + + @JsonProperty("id") + private Long id; + + /** + * + * (Required) + * + */ + @JsonProperty("type") + @NotNull + private PaymentMethodType type; + + /** + * + * (Required) + * + */ + @JsonProperty("cardNumber") + @NotNull + private String cardNumber; + + @JsonIgnore + @Valid + private Map additionalProperties = new LinkedHashMap(); + + protected final static Object NOT_FOUND_VALUE = new Object(); + + private final static long serialVersionUID = -8629217294484723648L; + + @JsonProperty("id") + public Long getId() { + return id; + } + + @JsonProperty("id") + public void setId(Long id) { + this.id = id; + } + + public PaymentMethod withId(Long id) { + this.id = id; + return this; + } + + /** + * + * (Required) + * + */ + @JsonProperty("type") + public PaymentMethodType getType() { + return type; + } + + /** + * + * (Required) + * + */ + @JsonProperty("type") + public void setType(PaymentMethodType type) { + this.type = type; + } + + public PaymentMethod withType(PaymentMethodType type) { + this.type = type; + return this; + } + + /** + * + * (Required) + * + */ + @JsonProperty("cardNumber") + public String getCardNumber() { + return cardNumber; + } + + /** + * + * (Required) + * + */ + @JsonProperty("cardNumber") + public void setCardNumber(String cardNumber) { + this.cardNumber = cardNumber; + } + + public PaymentMethod withCardNumber(String cardNumber) { + this.cardNumber = cardNumber; + return this; + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @JsonAnySetter + public void setAdditionalProperty(String name, Object value) { + this.additionalProperties.put(name, value); + } + + public PaymentMethod withAdditionalProperty(String name, Object value) { + this.additionalProperties.put(name, value); + return this; + } + + protected boolean declaredProperty(String name, Object value) { + if ("id".equals(name)) { + if (value instanceof Long) { + setId(((Long) value)); + } + else { + throw new IllegalArgumentException( + ("property \"id\" is of type \"java.lang.Long\", but got " + value.getClass().toString())); + } + return true; + } + else { + if ("type".equals(name)) { + if (value instanceof PaymentMethodType) { + setType(((PaymentMethodType) value)); + } + else { + throw new IllegalArgumentException( + ("property \"type\" is of type \"io.zenwave360.example.core.outbound.events.dtos.PaymentMethodType\", but got " + + value.getClass().toString())); + } + return true; + } + else { + if ("cardNumber".equals(name)) { + if (value instanceof String) { + setCardNumber(((String) value)); + } + else { + throw new IllegalArgumentException( + ("property \"cardNumber\" is of type \"java.lang.String\", but got " + + value.getClass().toString())); + } + return true; + } + else { + return false; + } + } + } + } + + protected Object declaredPropertyOrNotFound(String name, Object notFoundValue) { + if ("id".equals(name)) { + return getId(); + } + else { + if ("type".equals(name)) { + return getType(); + } + else { + if ("cardNumber".equals(name)) { + return getCardNumber(); + } + else { + return notFoundValue; + } + } + } + } + + @SuppressWarnings({ "unchecked" }) + public T get(String name) { + Object value = declaredPropertyOrNotFound(name, PaymentMethod.NOT_FOUND_VALUE); + if (PaymentMethod.NOT_FOUND_VALUE != value) { + return ((T) value); + } + else { + return ((T) getAdditionalProperties().get(name)); + } + } + + public void set(String name, Object value) { + if (!declaredProperty(name, value)) { + getAdditionalProperties().put(name, ((Object) value)); + } + } + + public PaymentMethod with(String name, Object value) { + if (!declaredProperty(name, value)) { + getAdditionalProperties().put(name, ((Object) value)); + } + return this; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(PaymentMethod.class.getName()) + .append('@') + .append(Integer.toHexString(System.identityHashCode(this))) + .append('['); + sb.append("id"); + sb.append('='); + sb.append(((this.id == null) ? "" : this.id)); + sb.append(','); + sb.append("type"); + sb.append('='); + sb.append(((this.type == null) ? "" : this.type)); + sb.append(','); + sb.append("cardNumber"); + sb.append('='); + sb.append(((this.cardNumber == null) ? "" : this.cardNumber)); + sb.append(','); + sb.append("additionalProperties"); + sb.append('='); + sb.append(((this.additionalProperties == null) ? "" : this.additionalProperties)); + sb.append(','); + if (sb.charAt((sb.length() - 1)) == ',') { + sb.setCharAt((sb.length() - 1), ']'); + } + else { + sb.append(']'); + } + return sb.toString(); + } + + @Override + public int hashCode() { + int result = 1; + result = ((result * 31) + ((this.id == null) ? 0 : this.id.hashCode())); + result = ((result * 31) + ((this.additionalProperties == null) ? 0 : this.additionalProperties.hashCode())); + result = ((result * 31) + ((this.type == null) ? 0 : this.type.hashCode())); + result = ((result * 31) + ((this.cardNumber == null) ? 0 : this.cardNumber.hashCode())); + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof PaymentMethod) == false) { + return false; + } + PaymentMethod rhs = ((PaymentMethod) other); + return (((((this.id == rhs.id) || ((this.id != null) && this.id.equals(rhs.id))) + && ((this.additionalProperties == rhs.additionalProperties) || ((this.additionalProperties != null) + && this.additionalProperties.equals(rhs.additionalProperties)))) + && ((this.type == rhs.type) || ((this.type != null) && this.type.equals(rhs.type)))) + && ((this.cardNumber == rhs.cardNumber) + || ((this.cardNumber != null) && this.cardNumber.equals(rhs.cardNumber)))); + } + +} diff --git a/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/json/PaymentMethodType.java b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/json/PaymentMethodType.java new file mode 100644 index 00000000..18cf7d60 --- /dev/null +++ b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/json/PaymentMethodType.java @@ -0,0 +1,49 @@ + +package org.springframework.modulith.events.scs.dtos.json; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import java.util.HashMap; +import java.util.Map; + +public enum PaymentMethodType { + + VISA("VISA"), MASTERCARD("MASTERCARD"); + + private final String value; + + private final static Map CONSTANTS = new HashMap(); + + static { + for (PaymentMethodType c : values()) { + CONSTANTS.put(c.value, c); + } + } + + PaymentMethodType(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + + @JsonValue + public String value() { + return this.value; + } + + @JsonCreator + public static PaymentMethodType fromValue(String value) { + PaymentMethodType constant = CONSTANTS.get(value); + if (constant == null) { + throw new IllegalArgumentException(value); + } + else { + return constant; + } + } + +} diff --git a/spring-modulith-events/spring-modulith-events-scs/src/test/resources/application.yml b/spring-modulith-events/spring-modulith-events-scs/src/test/resources/application.yml new file mode 100644 index 00000000..898bb877 --- /dev/null +++ b/spring-modulith-events/spring-modulith-events-scs/src/test/resources/application.yml @@ -0,0 +1,33 @@ +spring.main.banner-mode: OFF +spring.modulith.events.jdbc.schema-initialization.enabled: true +spring.modulith.events.externalization.enabled: true + +logging: + level: + root: WARN + io.zenwave360: DEBUG + org.springframework.jdbc: DEBUG + org.springframework.modulith: TRACE + +spring.datasource.url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE +spring.datasource.driverClassName: org.h2.Driver +spring.datasource.username: sa +spring.datasource.password: password +spring.h2.console.enabled: true +spring.sql.init.platform: h2 + +spring: + kafka: + producer: + key-serializer: org.apache.kafka.common.serialization.StringSerializer + cloud: + stream: + bindings: + customers-json-out-0: + destination: customers-json-topic + customers-avro-out-0: + destination: customers-avro-topic + content-type: application/*+avro + customers-avro-externalized-out-0: + destination: customers-avro-topic + content-type: application/*+avro