diff --git a/consumer-config.yaml b/consumer-config.yaml
new file mode 100644
index 0000000..face213
--- /dev/null
+++ b/consumer-config.yaml
@@ -0,0 +1,21 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: consumer-config
+data:
+ application.yml: |
+ spring:
+ pulsar:
+ client: # see here for all configurations: https://pulsar.apache.org/reference/#/4.0.x/client/client-configuration-client
+ clientConfig:
+ serviceUrl: "pulsar://host.docker.internal:6650"
+ consumer:
+ enabled: true
+ consumerConfig: # see here for all configurations: https://pulsar.apache.org/reference/#/4.0.x/client/client-configuration-producer
+ topicNames: "demo-test"
+ subscriptionName: "sub"
+ admin:
+ adminConfig: # Accepts the same key-value pair configurations as pulsar client: https://pulsar.apache.org/reference/#/4.0.x/client/client-configuration-client
+ serviceUrl: "http://host.docker.internal:8080"
+
+
\ No newline at end of file
diff --git a/consumer-pipeline.yaml b/consumer-pipeline.yaml
new file mode 100644
index 0000000..69c6c3b
--- /dev/null
+++ b/consumer-pipeline.yaml
@@ -0,0 +1,43 @@
+apiVersion: numaflow.numaproj.io/v1alpha1
+kind: Pipeline
+metadata:
+ name: consumer-pipeline
+spec:
+ limits:
+ readBatchSize: 1 # Change if you want a different batch size
+ vertices:
+ - name: in
+ scale:
+ min: 1
+ volumes:
+ - name: pulsar-config-volume
+ configMap:
+ name: consumer-config
+ items:
+ - key: application.yml
+ path: application.yml
+ source:
+ udsource:
+ container:
+ image: apache-pulsar-java:v0.3.0
+ args: [ "--spring.config.location=file:/conf/application.yml" ]
+ imagePullPolicy: Never
+ volumeMounts:
+ - name: pulsar-config-volume
+ mountPath: /conf
+ - name: p1
+ scale:
+ min: 1
+ udf:
+ builtin:
+ name: cat
+ - name: out
+ scale:
+ min: 1
+ sink:
+ log: {}
+ edges:
+ - from: in
+ to: p1
+ - from: p1
+ to: out
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
index cdaa34b..8453585 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,4 +1,3 @@
-version: '3.5'
services:
pulsar:
@@ -6,6 +5,9 @@ services:
command: bin/pulsar standalone
environment:
PULSAR_MEM: "-Xms512m -Xmx512m -XX:MaxDirectMemorySize=1g"
+ volumes:
+ - ./schema.avsc:/pulsar/schemas/schema.avsc
+
ports:
- "6650:6650"
diff --git a/pom.xml b/pom.xml
index 35e19df..484272b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -35,6 +35,11 @@
spring-boot-starter-test
test
+
+ org.apache.avro
+ avro
+ 1.10.2
+
@@ -96,6 +101,39 @@
+
+ org.apache.avro
+ avro-maven-plugin
+ 1.10.2
+
+
+ schemas
+ generate-sources
+
+ schema
+
+
+ ${project.basedir}/src/main/resources/
+ ${project.basedir}/src/main/java/
+ String
+ true
+ true
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ ${java.version}
+ ${java.version}
+ UTF-8
+
+ -parameters
+
+
+
diff --git a/producer-config.yaml b/producer-config.yaml
new file mode 100644
index 0000000..d743669
--- /dev/null
+++ b/producer-config.yaml
@@ -0,0 +1,18 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: producer-config
+data:
+ application.yml: |
+ spring:
+ pulsar:
+ client: # see here for all configurations: https://pulsar.apache.org/reference/#/4.0.x/client/client-configuration-client
+ clientConfig:
+ serviceUrl: "pulsar://host.docker.internal:6650"
+ producer:
+ enabled: true
+ producerConfig: # see here for all configurations: https://pulsar.apache.org/reference/#/4.0.x/client/client-configuration-producer
+ topicName: "demo-test"
+ sendTimeoutMs: 2000
+
+
\ No newline at end of file
diff --git a/producer-pipeline.yaml b/producer-pipeline.yaml
new file mode 100644
index 0000000..c37e7bf
--- /dev/null
+++ b/producer-pipeline.yaml
@@ -0,0 +1,44 @@
+apiVersion: numaflow.numaproj.io/v1alpha1
+kind: Pipeline
+metadata:
+ name: producer-pipeline
+spec:
+ vertices:
+ - name: in
+ scale:
+ min: 1
+ source:
+ generator:
+ rpu: 1
+ duration: 1s
+ msgSize: 20
+ - name: p1
+ scale:
+ min: 1
+ udf:
+ builtin:
+ name: cat
+ - name: out
+ scale:
+ min: 1
+ volumes: # Shared between containers that are part of the same pod, useful for sharing configurations
+ - name: pulsar-config-volume
+ configMap:
+ name: producer-config
+ items:
+ - key: application.yml
+ path: application.yml
+ sink:
+ udsink:
+ container:
+ image: apache-pulsar-java:v0.3.0 # TO DO: Replace with quay.io link
+ args: [ "--spring.config.location=file:/conf/application.yml" ] # Use external configuration file
+ imagePullPolicy: Never
+ volumeMounts:
+ - name: pulsar-config-volume
+ mountPath: /conf
+ edges:
+ - from: in
+ to: p1
+ - from: p1
+ to: out
\ No newline at end of file
diff --git a/schema.avsc b/schema.avsc
new file mode 100644
index 0000000..19e012b
--- /dev/null
+++ b/schema.avsc
@@ -0,0 +1,5 @@
+{
+ "type": "AVRO",
+ "schema": "{\"type\":\"record\",\"name\":\"numagen\",\"fields\":[{\"name\":\"Createdts\",\"type\":\"long\"},{\"name\":\"Data\",\"type\":[\"null\",{\"type\":\"record\",\"name\":\"DataRecord\",\"fields\":[{\"name\":\"padding\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"value\",\"type\":\"long\"}]}],\"default\":null}],\"aliases\":[\"numagen\"]}",
+ "properties": {}
+}
diff --git a/src/main/java/io/numaproj/pulsar/config/producer/PulsarProducerConfig.java b/src/main/java/io/numaproj/pulsar/config/producer/PulsarProducerConfig.java
index d92d96f..c9e76ee 100644
--- a/src/main/java/io/numaproj/pulsar/config/producer/PulsarProducerConfig.java
+++ b/src/main/java/io/numaproj/pulsar/config/producer/PulsarProducerConfig.java
@@ -1,16 +1,23 @@
package io.numaproj.pulsar.config.producer;
+import io.numaproj.pulsar.model.numagen;
import org.apache.pulsar.client.api.Producer;
import org.apache.pulsar.client.api.PulsarClient;
import org.apache.pulsar.client.api.Schema;
+import org.apache.pulsar.client.api.PulsarClientException;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
+import org.springframework.core.io.Resource;
+import org.apache.avro.Schema.Parser;
+import io.numaproj.pulsar.model.numagen;
import lombok.extern.slf4j.Slf4j;
+import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.UUID;
@@ -18,25 +25,29 @@
@Configuration
public class PulsarProducerConfig {
- @Autowired
- private Environment env;
-
- @Bean
- @ConditionalOnProperty(prefix = "spring.pulsar.producer", name = "enabled", havingValue = "true", matchIfMissing = false)
- public Producer pulsarProducer(PulsarClient pulsarClient, PulsarProducerProperties pulsarProducerProperties)
- throws Exception {
- String podName = env.getProperty("NUMAFLOW_POD", "pod-" + UUID.randomUUID());
- String producerName = "producerName";
-
- Map producerConfig = pulsarProducerProperties.getProducerConfig();
- if (producerConfig.containsKey(producerName)) {
- log.warn("User configured a 'producerName' in the config, but this can cause errors if multiple pods spin "
- + "up with the same name. Overriding with '{}'", podName);
+ @Autowired
+ private Environment env;
+
+ @Bean
+ @ConditionalOnProperty(prefix = "spring.pulsar.producer", name = "enabled", havingValue = "true", matchIfMissing = false)
+ public Producer pulsarProducer(PulsarClient pulsarClient,
+ PulsarProducerProperties pulsarProducerProperties)
+ throws Exception {
+ String podName = env.getProperty("NUMAFLOW_POD", "pod-" + UUID.randomUUID());
+ String producerName = "producerName";
+
+ Map producerConfig = pulsarProducerProperties.getProducerConfig();
+ if (producerConfig.containsKey(producerName)) {
+ log.warn("User configured a 'producerName' in the config, but this can cause errors if multiple pods spin "
+ + "up with the same name. Overriding with '{}'", podName);
+ }
+ producerConfig.put(producerName, podName);
+
+
+
+ // Use the schema from the Avro-generated class
+ return pulsarClient.newProducer(Schema.AVRO(numagen.class))
+ .loadConf(producerConfig)
+ .create();
}
- producerConfig.put(producerName, podName);
-
- return pulsarClient.newProducer(Schema.BYTES)
- .loadConf(producerConfig)
- .create();
- }
}
\ No newline at end of file
diff --git a/src/main/java/io/numaproj/pulsar/consumer/PulsarConsumerManager.java b/src/main/java/io/numaproj/pulsar/consumer/PulsarConsumerManager.java
index fa3573f..c6ca775 100644
--- a/src/main/java/io/numaproj/pulsar/consumer/PulsarConsumerManager.java
+++ b/src/main/java/io/numaproj/pulsar/consumer/PulsarConsumerManager.java
@@ -7,6 +7,8 @@
import org.apache.pulsar.client.api.PulsarClientException;
import org.apache.pulsar.client.api.Schema;
import org.apache.pulsar.client.api.SubscriptionType;
+import org.apache.pulsar.client.api.schema.GenericRecord;
+
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
@@ -33,10 +35,10 @@ public class PulsarConsumerManager {
private PulsarClient pulsarClient;
// The current consumer instance.
- private Consumer currentConsumer;
+ private Consumer currentConsumer;
// Returns the current consumer if it exists. If not, creates a new one.
- public Consumer getOrCreateConsumer(long count, long timeoutMillis)
+ public Consumer getOrCreateConsumer(long count, long timeoutMillis)
throws PulsarClientException {
if (currentConsumer != null) {
return currentConsumer;
@@ -48,7 +50,7 @@ public Consumer getOrCreateConsumer(long count, long timeoutMillis)
// than 2^63 - 1 which will cause an overflow
.build();
- currentConsumer = pulsarClient.newConsumer(Schema.BYTES)
+ currentConsumer = pulsarClient.newConsumer(Schema.AUTO_CONSUME())
.loadConf(pulsarConsumerProperties.getConsumerConfig())
.batchReceivePolicy(batchPolicy)
.subscriptionType(SubscriptionType.Shared) // Must be shared to support multiple pods
diff --git a/src/main/java/io/numaproj/pulsar/consumer/PulsarSource.java b/src/main/java/io/numaproj/pulsar/consumer/PulsarSource.java
index f0872fb..549e0d7 100644
--- a/src/main/java/io/numaproj/pulsar/consumer/PulsarSource.java
+++ b/src/main/java/io/numaproj/pulsar/consumer/PulsarSource.java
@@ -13,6 +13,7 @@
import org.apache.pulsar.client.api.Consumer;
import org.apache.pulsar.client.api.Messages;
import org.apache.pulsar.client.api.PulsarClientException;
+import org.apache.pulsar.client.api.schema.GenericRecord;
import org.apache.pulsar.client.admin.PulsarAdmin;
import org.apache.pulsar.client.admin.PulsarAdminException;
import org.apache.pulsar.common.policies.data.TopicStats;
@@ -29,6 +30,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import com.fasterxml.jackson.databind.ObjectMapper;
@Slf4j
@Component
@@ -36,7 +38,7 @@
public class PulsarSource extends Sourcer {
// Map tracking received messages (keyed by Pulsar message ID string)
- private final Map> messagesToAck = new HashMap<>();
+ private final Map> messagesToAck = new HashMap<>();
private Server server;
@@ -49,6 +51,8 @@ public class PulsarSource extends Sourcer {
@Autowired
PulsarConsumerProperties pulsarConsumerProperties;
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
@PostConstruct
public void startServer() throws Exception {
server = new Server(this);
@@ -58,73 +62,87 @@ public void startServer() throws Exception {
@Override
public void read(ReadRequest request, OutputObserver observer) {
- // If there are messages not acknowledged, return
- if (!messagesToAck.isEmpty()) {
- log.trace("messagesToAck not empty: {}", messagesToAck);
- return;
- }
-
- Consumer consumer = null;
-
try {
- // Obtain a consumer with the desired settings.
- consumer = pulsarConsumerManager.getOrCreateConsumer(request.getCount(), request.getTimeout().toMillis());
-
- Messages batchMessages = consumer.batchReceive();
-
- if (batchMessages == null || batchMessages.size() == 0) {
- log.trace("Received 0 messages, return early.");
+ if (!messagesToAck.isEmpty()) {
+ log.warn("Messages to ack is not empty. Size: {}. Returning early.", messagesToAck.size());
return;
}
- // Process each message in the batch.
- for (org.apache.pulsar.client.api.Message pMsg : batchMessages) {
- String msgId = pMsg.getMessageId().toString();
- log.info("Consumed Pulsar message [id: {}]: {}", pMsg.getMessageId(),
- new String(pMsg.getValue(), StandardCharsets.UTF_8));
+ Consumer consumer = pulsarConsumerManager.getOrCreateConsumer(request.getCount(),
+ request.getTimeout().toMillis());
- byte[] offsetBytes = msgId.getBytes(StandardCharsets.UTF_8);
- Offset offset = new Offset(offsetBytes);
-
- Message message = new Message(pMsg.getValue(), offset, Instant.now());
- observer.send(message);
+ Messages messages = consumer.batchReceive();
+ if (messages == null) {
+ log.debug("No messages received within timeout");
+ return;
+ }
- messagesToAck.put(msgId, pMsg);
+ for (org.apache.pulsar.client.api.Message msg : messages) {
+ String messageId = msg.getMessageId().toString();
+ messagesToAck.put(messageId, msg);
+
+ GenericRecord message = msg.getValue();
+
+ // Convert GenericRecord to Map recursively
+ Map messageData = new HashMap<>();
+ message.getFields().forEach(field -> {
+ String fieldName = field.getName();
+ Object fieldValue = message.getField(field);
+
+ // Handle nested GenericRecord
+ if (fieldValue instanceof GenericRecord) {
+ Map nestedMap = new HashMap<>();
+ GenericRecord nestedRecord = (GenericRecord) fieldValue;
+ nestedRecord.getFields().forEach(nestedField -> {
+ String nestedName = nestedField.getName();
+ Object nestedValue = nestedRecord.getField(nestedField);
+ nestedMap.put(nestedName, nestedValue);
+ });
+ messageData.put(fieldName, nestedMap);
+ } else {
+ messageData.put(fieldName, fieldValue);
+ }
+ });
+
+ // Convert the map to JSON
+ String jsonValue = objectMapper.writeValueAsString(messageData);
+ log.info("Sending message to observer: {}", jsonValue);
+
+ Message numaMessage = new Message(
+ jsonValue.getBytes(StandardCharsets.UTF_8),
+ new Offset(messageId.getBytes(StandardCharsets.UTF_8)),
+ Instant.ofEpochMilli(msg.getEventTime() > 0 ? msg.getEventTime() : msg.getPublishTime()));
+
+ observer.send(numaMessage);
}
- } catch (PulsarClientException e) {
- log.error("Failed to get consumer or receive messages from Pulsar", e);
- throw new RuntimeException("Failed to get consumer or receive messages from Pulsar", e);
+
+ } catch (Exception e) {
+ log.error("Error while reading messages", e);
}
}
@Override
public void ack(AckRequest request) {
- // Convert offsets to message ID strings for comparison
- Map requestOffsetMap = new HashMap<>(); // key: msgId, value: offset object
+ Map requestOffsetMap = new HashMap<>();
request.getOffsets().forEach(offset -> {
- // Offset value is a byte array so convert byte arr to string
String messageIdKey = new String(offset.getValue(), StandardCharsets.UTF_8);
requestOffsetMap.put(messageIdKey, offset);
});
- // Verify that the keys in messagesToAck match the message IDs from the request
if (!messagesToAck.keySet().equals(requestOffsetMap.keySet())) {
log.error("Mismatch in acknowledgment: internal pending IDs {} do not match requested ack IDs {}",
messagesToAck.keySet(), requestOffsetMap.keySet());
- // Return early without processing the ack to prevent any inconsistent state
return;
}
- // If the check passed, process each ack request
for (Map.Entry entry : requestOffsetMap.entrySet()) {
String messageIdKey = entry.getKey();
- org.apache.pulsar.client.api.Message pMsg = messagesToAck.get(messageIdKey);
+ org.apache.pulsar.client.api.Message pMsg = messagesToAck.get(messageIdKey);
if (pMsg != null) {
try {
- Consumer consumer = pulsarConsumerManager.getOrCreateConsumer(0, 0);
+ Consumer consumer = pulsarConsumerManager.getOrCreateConsumer(0, 0);
consumer.acknowledge(pMsg);
- log.info("Acknowledged Pulsar message with ID: {} and payload: {}",
- messageIdKey, new String(pMsg.getValue(), StandardCharsets.UTF_8));
+ log.info("Acknowledged Pulsar message with ID: {}", messageIdKey);
} catch (PulsarClientException e) {
log.error("Failed to acknowledge Pulsar message", e);
}
@@ -138,25 +156,19 @@ public void ack(AckRequest request) {
@Override
public long getPending() {
try {
- // TODO - If changing to support multiple topics, we need to update this
Set topicNames = (Set) pulsarConsumerProperties.getConsumerConfig().get("topicNames");
- String topicName = topicNames.iterator().next(); // Assumes there is only one topic name in the set
+ String topicName = topicNames.iterator().next();
String subscriptionName = (String) pulsarConsumerProperties.getConsumerConfig().get("subscriptionName");
int partitionCount = pulsarAdmin.topics().getPartitionedTopicMetadata(topicName).partitions;
if (partitionCount > 0) {
- // Topic is partitioned, so we should use partitionedStats
var partitionedStats = pulsarAdmin.topics().getPartitionedStats(topicName, false);
- // If the subscription exists at the partitioned level, get its aggregated
- // backlog
if (partitionedStats.getSubscriptions().containsKey(subscriptionName)) {
long backlog = partitionedStats.getSubscriptions().get(subscriptionName).getMsgBacklog();
log.info("Number of messages in the backlog (partitioned) for subscription {}: {}",
subscriptionName, backlog);
return backlog;
} else {
- // If subscription not found at top-level stats, sum the backlog across each
- // partition
long totalBacklog = partitionedStats.getPartitions().values().stream()
.mapToLong(ts -> {
var subStats = ts.getSubscriptions().get(subscriptionName);
@@ -169,7 +181,6 @@ public long getPending() {
return totalBacklog;
}
} else {
- // Non-partitioned topic–safe to call getStats directly
TopicStats topicStats = pulsarAdmin.topics().getStats(topicName);
SubscriptionStats subscriptionStats = topicStats.getSubscriptions().get(subscriptionName);
log.info("Number of messages in the backlog: {}", subscriptionStats.getMsgBacklog());
@@ -178,7 +189,6 @@ public long getPending() {
} catch (PulsarAdminException e) {
log.error("Error while fetching admin stats for pending messages", e);
- // Return a negative value to indicate no pending information
return -1;
}
}
@@ -187,19 +197,16 @@ public long getPending() {
public List getPartitions() {
try {
Set topicNames = (Set) pulsarConsumerProperties.getConsumerConfig().get("topicNames");
- // Assume single topic in the set
String topicName = topicNames.iterator().next();
int numPartitions = pulsarAdmin.topics().getPartitionedTopicMetadata(topicName).partitions;
log.info("Number of partitions reported by metadata for topic {}: {}", topicName, numPartitions);
- // If it's not partitioned, Pulsar returns 0 partitions
if (numPartitions < 1) {
log.warn("Topic {} is not reported as partitioned", topicName);
return List.of(0);
}
- // Otherwise, build the partition indexes from 0..(numPartitions-1)
List partitionIndexes = new ArrayList<>();
for (int i = 0; i < numPartitions; i++) {
partitionIndexes.add(i);
@@ -211,5 +218,4 @@ public List getPartitions() {
return defaultPartitions();
}
}
-
}
diff --git a/src/main/java/io/numaproj/pulsar/examples/NumagenExample.java b/src/main/java/io/numaproj/pulsar/examples/NumagenExample.java
new file mode 100644
index 0000000..b440365
--- /dev/null
+++ b/src/main/java/io/numaproj/pulsar/examples/NumagenExample.java
@@ -0,0 +1,97 @@
+package io.numaproj.pulsar.examples;
+
+import io.numaproj.pulsar.model.numagen;
+import io.numaproj.pulsar.model.DataRecord;
+import org.apache.pulsar.client.api.Producer;
+import org.apache.pulsar.client.api.PulsarClient;
+import org.apache.pulsar.client.api.Schema;
+import org.apache.pulsar.client.api.PulsarClientException;
+
+import java.util.concurrent.TimeUnit;
+
+public class NumagenExample {
+
+ public static void main(String[] args) {
+ try {
+ // Create Pulsar client
+ PulsarClient client = PulsarClient.builder()
+ .serviceUrl("pulsar://localhost:6650")
+ .build();
+
+ // Create producer
+ Producer producer = client.newProducer(Schema.AVRO(numagen.class))
+ .topic("persistent://public/default/numagen-topic")
+ .producerName("numagen-example-producer")
+ .sendTimeout(10, TimeUnit.SECONDS)
+ .create();
+
+ // Method 1: Using the builder pattern (recommended)
+ numagen message1 = numagen.newBuilder()
+ .setCreatedts(System.currentTimeMillis())
+ .setData(DataRecord.newBuilder()
+ .setValue(42L)
+ .setPadding("example")
+ .build())
+ .build();
+
+ // Send message synchronously
+ producer.send(message1);
+ System.out.println("Message 1 sent successfully");
+
+ // Method 2: Using constructor and setters
+ numagen message2 = new numagen();
+ message2.setCreatedts(System.currentTimeMillis());
+
+ DataRecord data = new DataRecord();
+ data.setValue(123L);
+ data.setPadding("another example");
+ message2.setData(data);
+
+ // Send message asynchronously
+ producer.sendAsync(message2)
+ .thenAccept(msgId -> {
+ System.out.println("Message 2 sent successfully, messageId: " + msgId);
+ })
+ .exceptionally(ex -> {
+ System.err.println("Failed to send message 2: " + ex);
+ return null;
+ });
+
+ // Method 3: Using the all-args constructor
+ DataRecord data3 = new DataRecord("third example", 789L);
+ numagen message3 = new numagen(System.currentTimeMillis(), data3);
+
+ // Send with key
+ producer.newMessage()
+ .key("message-3")
+ .value(message3)
+ .send();
+ System.out.println("Message 3 sent with key");
+
+ // Data can be null (it's optional in the schema)
+ numagen message4 = numagen.newBuilder()
+ .setCreatedts(System.currentTimeMillis())
+ .setData(null) // Data is optional
+ .build();
+
+ // Send with properties
+ producer.newMessage()
+ .property("messageType", "null-data")
+ .value(message4)
+ .sendAsync()
+ .thenAccept(msgId -> {
+ System.out.println("Message 4 sent with properties, messageId: " + msgId);
+ });
+
+ // Wait for async operations to complete
+ Thread.sleep(1000);
+
+ // Clean up
+ producer.close();
+ client.close();
+
+ } catch (PulsarClientException | InterruptedException e) {
+ System.err.println("Error in Pulsar operations: " + e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/numaproj/pulsar/model/DataRecord.java b/src/main/java/io/numaproj/pulsar/model/DataRecord.java
new file mode 100644
index 0000000..285241d
--- /dev/null
+++ b/src/main/java/io/numaproj/pulsar/model/DataRecord.java
@@ -0,0 +1,406 @@
+/**
+ * Autogenerated by Avro
+ *
+ * DO NOT EDIT DIRECTLY
+ */
+package io.numaproj.pulsar.model;
+
+import org.apache.avro.generic.GenericArray;
+import org.apache.avro.specific.SpecificData;
+import org.apache.avro.util.Utf8;
+import org.apache.avro.message.BinaryMessageEncoder;
+import org.apache.avro.message.BinaryMessageDecoder;
+import org.apache.avro.message.SchemaStore;
+
+@org.apache.avro.specific.AvroGenerated
+public class DataRecord extends org.apache.avro.specific.SpecificRecordBase implements org.apache.avro.specific.SpecificRecord {
+ private static final long serialVersionUID = -8694814375030929223L;
+ public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse("{\"type\":\"record\",\"name\":\"DataRecord\",\"namespace\":\"io.numaproj.pulsar.model\",\"fields\":[{\"name\":\"padding\",\"type\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}],\"default\":null},{\"name\":\"value\",\"type\":\"long\"}]}");
+ public static org.apache.avro.Schema getClassSchema() { return SCHEMA$; }
+
+ private static 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 DataRecord 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 DataRecord from a ByteBuffer.
+ * @param b a byte buffer holding serialized data for an instance of this class
+ * @return a DataRecord 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 DataRecord fromByteBuffer(
+ java.nio.ByteBuffer b) throws java.io.IOException {
+ return DECODER.decode(b);
+ }
+
+ private java.lang.String padding;
+ private long value;
+
+ /**
+ * 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 DataRecord() {}
+
+ /**
+ * All-args constructor.
+ * @param padding The new value for padding
+ * @param value The new value for value
+ */
+ public DataRecord(java.lang.String padding, java.lang.Long value) {
+ this.padding = padding;
+ this.value = value;
+ }
+
+ public org.apache.avro.specific.SpecificData getSpecificData() { return MODEL$; }
+ public org.apache.avro.Schema getSchema() { return SCHEMA$; }
+ // Used by DatumWriter. Applications should not call.
+ public java.lang.Object get(int field$) {
+ switch (field$) {
+ case 0: return padding;
+ case 1: return value;
+ default: throw new IndexOutOfBoundsException("Invalid index: " + field$);
+ }
+ }
+
+ // Used by DatumReader. Applications should not call.
+ @SuppressWarnings(value="unchecked")
+ public void put(int field$, java.lang.Object value$) {
+ switch (field$) {
+ case 0: padding = value$ != null ? value$.toString() : null; break;
+ case 1: value = (java.lang.Long)value$; break;
+ default: throw new IndexOutOfBoundsException("Invalid index: " + field$);
+ }
+ }
+
+ /**
+ * Gets the value of the 'padding' field.
+ * @return The value of the 'padding' field.
+ */
+ public java.lang.String getPadding() {
+ return padding;
+ }
+
+
+ /**
+ * Sets the value of the 'padding' field.
+ * @param value the value to set.
+ */
+ public void setPadding(java.lang.String value) {
+ this.padding = value;
+ }
+
+ /**
+ * Gets the value of the 'value' field.
+ * @return The value of the 'value' field.
+ */
+ public long getValue() {
+ return value;
+ }
+
+
+ /**
+ * Sets the value of the 'value' field.
+ * @param value the value to set.
+ */
+ public void setValue(long value) {
+ this.value = value;
+ }
+
+ /**
+ * Creates a new DataRecord RecordBuilder.
+ * @return A new DataRecord RecordBuilder
+ */
+ public static io.numaproj.pulsar.model.DataRecord.Builder newBuilder() {
+ return new io.numaproj.pulsar.model.DataRecord.Builder();
+ }
+
+ /**
+ * Creates a new DataRecord RecordBuilder by copying an existing Builder.
+ * @param other The existing builder to copy.
+ * @return A new DataRecord RecordBuilder
+ */
+ public static io.numaproj.pulsar.model.DataRecord.Builder newBuilder(io.numaproj.pulsar.model.DataRecord.Builder other) {
+ if (other == null) {
+ return new io.numaproj.pulsar.model.DataRecord.Builder();
+ } else {
+ return new io.numaproj.pulsar.model.DataRecord.Builder(other);
+ }
+ }
+
+ /**
+ * Creates a new DataRecord RecordBuilder by copying an existing DataRecord instance.
+ * @param other The existing instance to copy.
+ * @return A new DataRecord RecordBuilder
+ */
+ public static io.numaproj.pulsar.model.DataRecord.Builder newBuilder(io.numaproj.pulsar.model.DataRecord other) {
+ if (other == null) {
+ return new io.numaproj.pulsar.model.DataRecord.Builder();
+ } else {
+ return new io.numaproj.pulsar.model.DataRecord.Builder(other);
+ }
+ }
+
+ /**
+ * RecordBuilder for DataRecord instances.
+ */
+ @org.apache.avro.specific.AvroGenerated
+ public static class Builder extends org.apache.avro.specific.SpecificRecordBuilderBase
+ implements org.apache.avro.data.RecordBuilder {
+
+ private java.lang.String padding;
+ private long value;
+
+ /** Creates a new Builder */
+ private Builder() {
+ super(SCHEMA$);
+ }
+
+ /**
+ * Creates a Builder by copying an existing Builder.
+ * @param other The existing Builder to copy.
+ */
+ private Builder(io.numaproj.pulsar.model.DataRecord.Builder other) {
+ super(other);
+ if (isValidValue(fields()[0], other.padding)) {
+ this.padding = data().deepCopy(fields()[0].schema(), other.padding);
+ fieldSetFlags()[0] = other.fieldSetFlags()[0];
+ }
+ if (isValidValue(fields()[1], other.value)) {
+ this.value = data().deepCopy(fields()[1].schema(), other.value);
+ fieldSetFlags()[1] = other.fieldSetFlags()[1];
+ }
+ }
+
+ /**
+ * Creates a Builder by copying an existing DataRecord instance
+ * @param other The existing instance to copy.
+ */
+ private Builder(io.numaproj.pulsar.model.DataRecord other) {
+ super(SCHEMA$);
+ if (isValidValue(fields()[0], other.padding)) {
+ this.padding = data().deepCopy(fields()[0].schema(), other.padding);
+ fieldSetFlags()[0] = true;
+ }
+ if (isValidValue(fields()[1], other.value)) {
+ this.value = data().deepCopy(fields()[1].schema(), other.value);
+ fieldSetFlags()[1] = true;
+ }
+ }
+
+ /**
+ * Gets the value of the 'padding' field.
+ * @return The value.
+ */
+ public java.lang.String getPadding() {
+ return padding;
+ }
+
+
+ /**
+ * Sets the value of the 'padding' field.
+ * @param value The value of 'padding'.
+ * @return This builder.
+ */
+ public io.numaproj.pulsar.model.DataRecord.Builder setPadding(java.lang.String value) {
+ validate(fields()[0], value);
+ this.padding = value;
+ fieldSetFlags()[0] = true;
+ return this;
+ }
+
+ /**
+ * Checks whether the 'padding' field has been set.
+ * @return True if the 'padding' field has been set, false otherwise.
+ */
+ public boolean hasPadding() {
+ return fieldSetFlags()[0];
+ }
+
+
+ /**
+ * Clears the value of the 'padding' field.
+ * @return This builder.
+ */
+ public io.numaproj.pulsar.model.DataRecord.Builder clearPadding() {
+ padding = null;
+ fieldSetFlags()[0] = false;
+ return this;
+ }
+
+ /**
+ * Gets the value of the 'value' field.
+ * @return The value.
+ */
+ public long getValue() {
+ return value;
+ }
+
+
+ /**
+ * Sets the value of the 'value' field.
+ * @param value The value of 'value'.
+ * @return This builder.
+ */
+ public io.numaproj.pulsar.model.DataRecord.Builder setValue(long value) {
+ validate(fields()[1], value);
+ this.value = value;
+ fieldSetFlags()[1] = true;
+ return this;
+ }
+
+ /**
+ * Checks whether the 'value' field has been set.
+ * @return True if the 'value' field has been set, false otherwise.
+ */
+ public boolean hasValue() {
+ return fieldSetFlags()[1];
+ }
+
+
+ /**
+ * Clears the value of the 'value' field.
+ * @return This builder.
+ */
+ public io.numaproj.pulsar.model.DataRecord.Builder clearValue() {
+ fieldSetFlags()[1] = false;
+ return this;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public DataRecord build() {
+ try {
+ DataRecord record = new DataRecord();
+ record.padding = fieldSetFlags()[0] ? this.padding : (java.lang.String) defaultValue(fields()[0]);
+ record.value = fieldSetFlags()[1] ? this.value : (java.lang.Long) defaultValue(fields()[1]);
+ return record;
+ } catch (org.apache.avro.AvroMissingFieldException e) {
+ throw e;
+ } catch (java.lang.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
+ {
+ if (this.padding == null) {
+ out.writeIndex(0);
+ out.writeNull();
+ } else {
+ out.writeIndex(1);
+ out.writeString(this.padding);
+ }
+
+ out.writeLong(this.value);
+
+ }
+
+ @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) {
+ if (in.readIndex() != 1) {
+ in.readNull();
+ this.padding = null;
+ } else {
+ this.padding = in.readString();
+ }
+
+ this.value = in.readLong();
+
+ } else {
+ for (int i = 0; i < 2; i++) {
+ switch (fieldOrder[i].pos()) {
+ case 0:
+ if (in.readIndex() != 1) {
+ in.readNull();
+ this.padding = null;
+ } else {
+ this.padding = in.readString();
+ }
+ break;
+
+ case 1:
+ this.value = in.readLong();
+ break;
+
+ default:
+ throw new java.io.IOException("Corrupt ResolvingDecoder.");
+ }
+ }
+ }
+ }
+}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/java/io/numaproj/pulsar/model/numagen.java b/src/main/java/io/numaproj/pulsar/model/numagen.java
new file mode 100644
index 0000000..7e98a0f
--- /dev/null
+++ b/src/main/java/io/numaproj/pulsar/model/numagen.java
@@ -0,0 +1,462 @@
+/**
+ * Autogenerated by Avro
+ *
+ * DO NOT EDIT DIRECTLY
+ */
+package io.numaproj.pulsar.model;
+
+import org.apache.avro.generic.GenericArray;
+import org.apache.avro.specific.SpecificData;
+import org.apache.avro.util.Utf8;
+import org.apache.avro.message.BinaryMessageEncoder;
+import org.apache.avro.message.BinaryMessageDecoder;
+import org.apache.avro.message.SchemaStore;
+
+@org.apache.avro.specific.AvroGenerated
+public class numagen extends org.apache.avro.specific.SpecificRecordBase implements org.apache.avro.specific.SpecificRecord {
+ private static final long serialVersionUID = 7518030864920628550L;
+ public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse("{\"type\":\"record\",\"name\":\"numagen\",\"namespace\":\"io.numaproj.pulsar.model\",\"fields\":[{\"name\":\"Createdts\",\"type\":\"long\"},{\"name\":\"Data\",\"type\":[\"null\",{\"type\":\"record\",\"name\":\"DataRecord\",\"fields\":[{\"name\":\"padding\",\"type\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}],\"default\":null},{\"name\":\"value\",\"type\":\"long\"}]}],\"default\":null}],\"aliases\":[\"numagen\"]}");
+ public static org.apache.avro.Schema getClassSchema() { return SCHEMA$; }
+
+ private static 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 numagen 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 numagen from a ByteBuffer.
+ * @param b a byte buffer holding serialized data for an instance of this class
+ * @return a numagen 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 numagen fromByteBuffer(
+ java.nio.ByteBuffer b) throws java.io.IOException {
+ return DECODER.decode(b);
+ }
+
+ private long Createdts;
+ private io.numaproj.pulsar.model.DataRecord Data;
+
+ /**
+ * 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 numagen() {}
+
+ /**
+ * All-args constructor.
+ * @param Createdts The new value for Createdts
+ * @param Data The new value for Data
+ */
+ public numagen(java.lang.Long Createdts, io.numaproj.pulsar.model.DataRecord Data) {
+ this.Createdts = Createdts;
+ this.Data = Data;
+ }
+
+ public org.apache.avro.specific.SpecificData getSpecificData() { return MODEL$; }
+ public org.apache.avro.Schema getSchema() { return SCHEMA$; }
+ // Used by DatumWriter. Applications should not call.
+ public java.lang.Object get(int field$) {
+ switch (field$) {
+ case 0: return Createdts;
+ case 1: return Data;
+ default: throw new IndexOutOfBoundsException("Invalid index: " + field$);
+ }
+ }
+
+ // Used by DatumReader. Applications should not call.
+ @SuppressWarnings(value="unchecked")
+ public void put(int field$, java.lang.Object value$) {
+ switch (field$) {
+ case 0: Createdts = (java.lang.Long)value$; break;
+ case 1: Data = (io.numaproj.pulsar.model.DataRecord)value$; break;
+ default: throw new IndexOutOfBoundsException("Invalid index: " + field$);
+ }
+ }
+
+ /**
+ * Gets the value of the 'Createdts' field.
+ * @return The value of the 'Createdts' field.
+ */
+ public long getCreatedts() {
+ return Createdts;
+ }
+
+
+ /**
+ * Sets the value of the 'Createdts' field.
+ * @param value the value to set.
+ */
+ public void setCreatedts(long value) {
+ this.Createdts = value;
+ }
+
+ /**
+ * Gets the value of the 'Data' field.
+ * @return The value of the 'Data' field.
+ */
+ public io.numaproj.pulsar.model.DataRecord getData() {
+ return Data;
+ }
+
+
+ /**
+ * Sets the value of the 'Data' field.
+ * @param value the value to set.
+ */
+ public void setData(io.numaproj.pulsar.model.DataRecord value) {
+ this.Data = value;
+ }
+
+ /**
+ * Creates a new numagen RecordBuilder.
+ * @return A new numagen RecordBuilder
+ */
+ public static io.numaproj.pulsar.model.numagen.Builder newBuilder() {
+ return new io.numaproj.pulsar.model.numagen.Builder();
+ }
+
+ /**
+ * Creates a new numagen RecordBuilder by copying an existing Builder.
+ * @param other The existing builder to copy.
+ * @return A new numagen RecordBuilder
+ */
+ public static io.numaproj.pulsar.model.numagen.Builder newBuilder(io.numaproj.pulsar.model.numagen.Builder other) {
+ if (other == null) {
+ return new io.numaproj.pulsar.model.numagen.Builder();
+ } else {
+ return new io.numaproj.pulsar.model.numagen.Builder(other);
+ }
+ }
+
+ /**
+ * Creates a new numagen RecordBuilder by copying an existing numagen instance.
+ * @param other The existing instance to copy.
+ * @return A new numagen RecordBuilder
+ */
+ public static io.numaproj.pulsar.model.numagen.Builder newBuilder(io.numaproj.pulsar.model.numagen other) {
+ if (other == null) {
+ return new io.numaproj.pulsar.model.numagen.Builder();
+ } else {
+ return new io.numaproj.pulsar.model.numagen.Builder(other);
+ }
+ }
+
+ /**
+ * RecordBuilder for numagen instances.
+ */
+ @org.apache.avro.specific.AvroGenerated
+ public static class Builder extends org.apache.avro.specific.SpecificRecordBuilderBase
+ implements org.apache.avro.data.RecordBuilder {
+
+ private long Createdts;
+ private io.numaproj.pulsar.model.DataRecord Data;
+ private io.numaproj.pulsar.model.DataRecord.Builder DataBuilder;
+
+ /** Creates a new Builder */
+ private Builder() {
+ super(SCHEMA$);
+ }
+
+ /**
+ * Creates a Builder by copying an existing Builder.
+ * @param other The existing Builder to copy.
+ */
+ private Builder(io.numaproj.pulsar.model.numagen.Builder other) {
+ super(other);
+ if (isValidValue(fields()[0], other.Createdts)) {
+ this.Createdts = data().deepCopy(fields()[0].schema(), other.Createdts);
+ fieldSetFlags()[0] = other.fieldSetFlags()[0];
+ }
+ if (isValidValue(fields()[1], other.Data)) {
+ this.Data = data().deepCopy(fields()[1].schema(), other.Data);
+ fieldSetFlags()[1] = other.fieldSetFlags()[1];
+ }
+ if (other.hasDataBuilder()) {
+ this.DataBuilder = io.numaproj.pulsar.model.DataRecord.newBuilder(other.getDataBuilder());
+ }
+ }
+
+ /**
+ * Creates a Builder by copying an existing numagen instance
+ * @param other The existing instance to copy.
+ */
+ private Builder(io.numaproj.pulsar.model.numagen other) {
+ super(SCHEMA$);
+ if (isValidValue(fields()[0], other.Createdts)) {
+ this.Createdts = data().deepCopy(fields()[0].schema(), other.Createdts);
+ fieldSetFlags()[0] = true;
+ }
+ if (isValidValue(fields()[1], other.Data)) {
+ this.Data = data().deepCopy(fields()[1].schema(), other.Data);
+ fieldSetFlags()[1] = true;
+ }
+ this.DataBuilder = null;
+ }
+
+ /**
+ * Gets the value of the 'Createdts' field.
+ * @return The value.
+ */
+ public long getCreatedts() {
+ return Createdts;
+ }
+
+
+ /**
+ * Sets the value of the 'Createdts' field.
+ * @param value The value of 'Createdts'.
+ * @return This builder.
+ */
+ public io.numaproj.pulsar.model.numagen.Builder setCreatedts(long value) {
+ validate(fields()[0], value);
+ this.Createdts = value;
+ fieldSetFlags()[0] = true;
+ return this;
+ }
+
+ /**
+ * Checks whether the 'Createdts' field has been set.
+ * @return True if the 'Createdts' field has been set, false otherwise.
+ */
+ public boolean hasCreatedts() {
+ return fieldSetFlags()[0];
+ }
+
+
+ /**
+ * Clears the value of the 'Createdts' field.
+ * @return This builder.
+ */
+ public io.numaproj.pulsar.model.numagen.Builder clearCreatedts() {
+ fieldSetFlags()[0] = false;
+ return this;
+ }
+
+ /**
+ * Gets the value of the 'Data' field.
+ * @return The value.
+ */
+ public io.numaproj.pulsar.model.DataRecord getData() {
+ return Data;
+ }
+
+
+ /**
+ * Sets the value of the 'Data' field.
+ * @param value The value of 'Data'.
+ * @return This builder.
+ */
+ public io.numaproj.pulsar.model.numagen.Builder setData(io.numaproj.pulsar.model.DataRecord value) {
+ validate(fields()[1], value);
+ this.DataBuilder = null;
+ this.Data = value;
+ fieldSetFlags()[1] = true;
+ return this;
+ }
+
+ /**
+ * Checks whether the 'Data' field has been set.
+ * @return True if the 'Data' field has been set, false otherwise.
+ */
+ public boolean hasData() {
+ return fieldSetFlags()[1];
+ }
+
+ /**
+ * Gets the Builder instance for the 'Data' field and creates one if it doesn't exist yet.
+ * @return This builder.
+ */
+ public io.numaproj.pulsar.model.DataRecord.Builder getDataBuilder() {
+ if (DataBuilder == null) {
+ if (hasData()) {
+ setDataBuilder(io.numaproj.pulsar.model.DataRecord.newBuilder(Data));
+ } else {
+ setDataBuilder(io.numaproj.pulsar.model.DataRecord.newBuilder());
+ }
+ }
+ return DataBuilder;
+ }
+
+ /**
+ * Sets the Builder instance for the 'Data' field
+ * @param value The builder instance that must be set.
+ * @return This builder.
+ */
+
+ public io.numaproj.pulsar.model.numagen.Builder setDataBuilder(io.numaproj.pulsar.model.DataRecord.Builder value) {
+ clearData();
+ DataBuilder = value;
+ return this;
+ }
+
+ /**
+ * Checks whether the 'Data' field has an active Builder instance
+ * @return True if the 'Data' field has an active Builder instance
+ */
+ public boolean hasDataBuilder() {
+ return DataBuilder != null;
+ }
+
+ /**
+ * Clears the value of the 'Data' field.
+ * @return This builder.
+ */
+ public io.numaproj.pulsar.model.numagen.Builder clearData() {
+ Data = null;
+ DataBuilder = null;
+ fieldSetFlags()[1] = false;
+ return this;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public numagen build() {
+ try {
+ numagen record = new numagen();
+ record.Createdts = fieldSetFlags()[0] ? this.Createdts : (java.lang.Long) defaultValue(fields()[0]);
+ if (DataBuilder != null) {
+ try {
+ record.Data = this.DataBuilder.build();
+ } catch (org.apache.avro.AvroMissingFieldException e) {
+ e.addParentField(record.getSchema().getField("Data"));
+ throw e;
+ }
+ } else {
+ record.Data = fieldSetFlags()[1] ? this.Data : (io.numaproj.pulsar.model.DataRecord) defaultValue(fields()[1]);
+ }
+ return record;
+ } catch (org.apache.avro.AvroMissingFieldException e) {
+ throw e;
+ } catch (java.lang.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.writeLong(this.Createdts);
+
+ if (this.Data == null) {
+ out.writeIndex(0);
+ out.writeNull();
+ } else {
+ out.writeIndex(1);
+ this.Data.customEncode(out);
+ }
+
+ }
+
+ @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.Createdts = in.readLong();
+
+ if (in.readIndex() != 1) {
+ in.readNull();
+ this.Data = null;
+ } else {
+ if (this.Data == null) {
+ this.Data = new io.numaproj.pulsar.model.DataRecord();
+ }
+ this.Data.customDecode(in);
+ }
+
+ } else {
+ for (int i = 0; i < 2; i++) {
+ switch (fieldOrder[i].pos()) {
+ case 0:
+ this.Createdts = in.readLong();
+ break;
+
+ case 1:
+ if (in.readIndex() != 1) {
+ in.readNull();
+ this.Data = null;
+ } else {
+ if (this.Data == null) {
+ this.Data = new io.numaproj.pulsar.model.DataRecord();
+ }
+ this.Data.customDecode(in);
+ }
+ break;
+
+ default:
+ throw new java.io.IOException("Corrupt ResolvingDecoder.");
+ }
+ }
+ }
+ }
+}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/java/io/numaproj/pulsar/producer/DataRecord.java b/src/main/java/io/numaproj/pulsar/producer/DataRecord.java
new file mode 100644
index 0000000..de78e64
--- /dev/null
+++ b/src/main/java/io/numaproj/pulsar/producer/DataRecord.java
@@ -0,0 +1,20 @@
+package io.numaproj.pulsar.producer;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.AllArgsConstructor;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonAlias;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class DataRecord {
+ @JsonProperty("padding")
+ @JsonAlias("Padding")
+ private String padding;
+
+ @JsonProperty("value")
+ @JsonAlias("Value")
+ private Long value;
+}
\ No newline at end of file
diff --git a/src/main/java/io/numaproj/pulsar/producer/NumagenMessage.java b/src/main/java/io/numaproj/pulsar/producer/NumagenMessage.java
new file mode 100644
index 0000000..3f1fb54
--- /dev/null
+++ b/src/main/java/io/numaproj/pulsar/producer/NumagenMessage.java
@@ -0,0 +1,20 @@
+package io.numaproj.pulsar.producer;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.AllArgsConstructor;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonAlias;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class NumagenMessage {
+ @JsonProperty("createdts")
+ @JsonAlias("Createdts")
+ private Long createdts;
+
+ @JsonProperty("data")
+ @JsonAlias("Data")
+ private DataRecord data;
+}
\ No newline at end of file
diff --git a/src/main/java/io/numaproj/pulsar/producer/PulsarSink.java b/src/main/java/io/numaproj/pulsar/producer/PulsarSink.java
index 0fe7f51..4fab6ba 100644
--- a/src/main/java/io/numaproj/pulsar/producer/PulsarSink.java
+++ b/src/main/java/io/numaproj/pulsar/producer/PulsarSink.java
@@ -13,12 +13,16 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import io.numaproj.pulsar.model.numagen;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
+import java.nio.charset.StandardCharsets;
@Slf4j
@Component
@@ -26,14 +30,16 @@
public class PulsarSink extends Sinker {
@Autowired
- private Producer producer;
+ private Producer producer;
@Autowired
private PulsarClient pulsarClient;
private Server server;
+ private final ObjectMapper objectMapper = new ObjectMapper()
+ .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
- @PostConstruct // starts server automatically when the spring context initializes
+ @PostConstruct
public void startServer() throws Exception {
server = new Server(this);
server.start();
@@ -43,8 +49,8 @@ public void startServer() throws Exception {
@Override
public ResponseList processMessages(DatumIterator datumIterator) {
ResponseList.ResponseListBuilder responseListBuilder = ResponseList.newBuilder();
-
List> futures = new ArrayList<>();
+
while (true) {
Datum datum;
try {
@@ -53,33 +59,47 @@ public ResponseList processMessages(DatumIterator datumIterator) {
Thread.currentThread().interrupt();
continue;
}
- // null means the iterator is closed, so we break
if (datum == null) {
break;
}
- final byte[] msg = datum.getValue();
final String msgId = datum.getId();
- // Won't wait for broker to confirm receipt of msg before continuing
- // sendSync returns CompletableFuture which will complete when broker ack
- CompletableFuture future = producer.sendAsync(msg)
- .thenAccept(messageId -> {
- log.info("Processed message ID: {}, Content: {}", msgId, new String(msg));
- responseListBuilder.addResponse(Response.responseOK(msgId));
- })
- .exceptionally(ex -> {
- log.error("Error processing message ID {}: {}", msgId, ex.getMessage(), ex);
- responseListBuilder.addResponse(Response.responseFailure(msgId, ex.getMessage()));
- return null;
- });
-
- futures.add(future);
+ try {
+ log.debug("Processing message ID: {}, content length: {}", msgId, datum.getValue().length);
+
+ // Parse the incoming JSON to our Avro-generated class
+ String jsonContent = new String(datum.getValue(), StandardCharsets.UTF_8);
+ log.info("Incoming JSON content: {}", jsonContent);
+
+ numagen message = objectMapper.readValue(jsonContent, numagen.class);
+
+ // Log the message that will be sent
+ log.info("Sending message - createdts: {}, data.value: {}, data.padding: {}",
+ message.getCreatedts(),
+ message.getData() != null ? message.getData().getValue() : "null",
+ message.getData() != null ? message.getData().getPadding() : "null");
+
+ // Send the message
+ CompletableFuture future = producer.sendAsync(message)
+ .thenAccept(messageId -> {
+ log.info("Processed message ID: {}, data sent successfully", msgId);
+ responseListBuilder.addResponse(Response.responseOK(msgId));
+ })
+ .exceptionally(ex -> {
+ log.error("Error processing message ID {}: {}", msgId, ex.getMessage(), ex);
+ responseListBuilder.addResponse(Response.responseFailure(msgId, ex.getMessage()));
+ return null;
+ });
+
+ futures.add(future);
+ } catch (Exception e) {
+ log.error("Exception during message processing for ID {}: {}", msgId, e.getMessage(), e);
+ responseListBuilder.addResponse(Response.responseFailure(msgId, e.getMessage()));
+ }
}
- // Wait for all sends to complete
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
-
return responseListBuilder.build();
}
diff --git a/src/main/resources/schema.avsc b/src/main/resources/schema.avsc
new file mode 100644
index 0000000..3ae6626
--- /dev/null
+++ b/src/main/resources/schema.avsc
@@ -0,0 +1,39 @@
+{
+ "type": "record",
+ "name": "numagen",
+ "namespace": "io.numaproj.pulsar.model",
+ "fields": [
+ {
+ "name": "Createdts",
+ "type": "long"
+ },
+ {
+ "name": "Data",
+ "type": [
+ "null",
+ {
+ "type": "record",
+ "name": "DataRecord",
+ "fields": [
+ {
+ "name": "padding",
+ "type": [
+ "null",
+ "string"
+ ],
+ "default": null
+ },
+ {
+ "name": "value",
+ "type": "long"
+ }
+ ]
+ }
+ ],
+ "default": null
+ }
+ ],
+ "aliases": [
+ "numagen"
+ ]
+}
\ No newline at end of file
diff --git a/src/main/resources/static/just-schema.json b/src/main/resources/static/just-schema.json
new file mode 100644
index 0000000..f7dfbf1
--- /dev/null
+++ b/src/main/resources/static/just-schema.json
@@ -0,0 +1,30 @@
+{
+ "fields": [
+ {
+ "name": "Data",
+ "type": {
+ "fields": [
+ {
+ "name": "value",
+ "type": "long"
+ },
+ {
+ "name": "padding",
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ ],
+ "name": "Data",
+ "type": "record"
+ }
+ },
+ {
+ "name": "Createdts",
+ "type": "long"
+ }
+ ],
+ "name": "numagen",
+ "type": "record"
+ }
diff --git a/src/test/java/io/numaproj/pulsar/config/producer/PulsarProducerConfigTest.java b/src/test/java/io/numaproj/pulsar/config/producer/PulsarProducerConfigTest.java
index b24a799..678f129 100644
--- a/src/test/java/io/numaproj/pulsar/config/producer/PulsarProducerConfigTest.java
+++ b/src/test/java/io/numaproj/pulsar/config/producer/PulsarProducerConfigTest.java
@@ -1,169 +1,169 @@
-package io.numaproj.pulsar.config.producer;
-
-import org.apache.pulsar.client.api.Producer;
-import org.apache.pulsar.client.api.ProducerBuilder;
-import org.apache.pulsar.client.api.PulsarClient;
-import org.apache.pulsar.client.api.Schema;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.core.env.Environment;
-import org.springframework.test.util.ReflectionTestUtils;
-
-import io.numaproj.pulsar.config.client.PulsarClientConfig;
-import io.numaproj.pulsar.config.client.PulsarClientProperties;
-
-import static org.junit.Assert.*;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyMap;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.*;
-
-import java.util.HashMap;
-import java.util.Map;
-
-@SpringBootTest(classes = PulsarProducerConfig.class)
-public class PulsarProducerConfigTest {
-
- private PulsarProducerConfig pulsarProducerConfig;
- private Environment mockEnvironment;
-
- // Objects used only by specific test groups
- private PulsarProducerConfig spiedConfig;
- private PulsarClient mockClient;
- private PulsarProducerProperties mockProducerProperties;
- private ProducerBuilder mockProducerBuilder;
- private Producer mockProducer;
-
- @Before
- public void setUp() throws Exception {
- pulsarProducerConfig = new PulsarProducerConfig();
- mockEnvironment = mock(Environment.class);
- ReflectionTestUtils.setField(pulsarProducerConfig, "env", mockEnvironment);
-
- mockProducerProperties = mock(PulsarProducerProperties.class);
- mockClient = mock(PulsarClient.class);
-
- spiedConfig = spy(pulsarProducerConfig);
- PulsarClientConfig mockClientConfig = mock(PulsarClientConfig.class);
- doReturn(mockClient).when(mockClientConfig).pulsarClient(any(PulsarClientProperties.class));
-
- @SuppressWarnings("unchecked")
- ProducerBuilder builder = mock(ProducerBuilder.class);
- mockProducerBuilder = builder;
-
- mockProducer = mock(Producer.class);
-
- when(mockClient.newProducer(Schema.BYTES)).thenReturn(mockProducerBuilder);
- when(mockProducerBuilder.create()).thenReturn(mockProducer);
- when(mockProducerBuilder.loadConf(anyMap())).thenReturn(mockProducerBuilder);
- }
-
- @After
- public void tearDown() {
- pulsarProducerConfig = null;
- spiedConfig = null;
- mockProducerProperties = null;
- mockClient = null;
- mockProducerBuilder = null;
- mockProducer = null;
- mockEnvironment = null;
- }
- // Test to successfully create Producer bean with valid configuration properties
- @Test
- public void pulsarProducer_validConfig() throws Exception {
- Map producerConfig = new HashMap<>();
- producerConfig.put("topicName", "test-topic");
- when(mockProducerProperties.getProducerConfig()).thenReturn(producerConfig);
-
- Producer producer = spiedConfig.pulsarProducer(mockClient, mockProducerProperties);
-
- assertNotNull("Producer should be created", producer);
-
- verify(mockProducerBuilder).loadConf(argThat(map -> "test-topic".equals(map.get("topicName"))));
- verify(mockProducerBuilder).create();
- verify(mockProducerProperties).getProducerConfig();
- }
-
- // Test which ensures an error is thrown if pulsar producer isn't created with
- // topicName
- @Test
- public void pulsarProducer_missingTopicName_throwsException() throws Exception {
- when(mockProducerProperties.getProducerConfig()).thenReturn(new HashMap<>());
-
- String expectedErrorSubstring = "Topic name must be set on the producer builder";
- when(mockProducerBuilder.create())
- .thenThrow(new IllegalArgumentException(expectedErrorSubstring));
-
- IllegalArgumentException exception = assertThrows(
- IllegalArgumentException.class,
- () -> pulsarProducerConfig.pulsarProducer(mockClient, mockProducerProperties));
-
- assertTrue(exception.getMessage().contains(expectedErrorSubstring));
- }
-
- // Test for environment variable is set, and user does NOT specify producerName
- @Test
- public void pulsarProducer_ProducerNameFromEnvVarNoUserConfig() throws Exception {
- final String envPodName = "NUMAFLOW_POD_VALUE";
- when(mockEnvironment.getProperty(eq("NUMAFLOW_POD"), anyString())).thenReturn(envPodName);
-
- Map emptyConfig = new HashMap<>();
- emptyConfig.put("topicName", "test-topic");
- when(mockProducerProperties.getProducerConfig()).thenReturn(emptyConfig);
-
- Producer producer = spiedConfig.pulsarProducer(mockClient, mockProducerProperties);
-
- assertNotNull(producer);
- // Check that the "producerName" is set to envPodName
- ArgumentCaptor