Add AVRO and (inline) KeyValue support for PulsarListener
This commit is contained in:
committed by
Chris Bono
parent
b188436ab5
commit
c2732c6ee6
@@ -20,13 +20,16 @@ import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.pulsar.client.api.Consumer;
|
||||
import org.apache.pulsar.client.api.Message;
|
||||
import org.apache.pulsar.client.api.Messages;
|
||||
import org.apache.pulsar.client.api.Schema;
|
||||
import org.apache.pulsar.client.impl.schema.AvroSchema;
|
||||
import org.apache.pulsar.client.impl.schema.JSONSchema;
|
||||
import org.apache.pulsar.common.schema.KeyValueEncodingType;
|
||||
import org.apache.pulsar.common.schema.SchemaType;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
@@ -37,6 +40,7 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.messaging.converter.SmartMessageConverter;
|
||||
import org.springframework.messaging.handler.annotation.support.MessageHandlerMethodFactory;
|
||||
import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
|
||||
import org.springframework.pulsar.core.SchemaUtils;
|
||||
import org.springframework.pulsar.listener.Acknowledgement;
|
||||
import org.springframework.pulsar.listener.ConcurrentPulsarMessageListenerContainer;
|
||||
import org.springframework.pulsar.listener.PulsarContainerProperties;
|
||||
@@ -56,6 +60,7 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @param <V> Message payload type
|
||||
* @author Soby Chacko
|
||||
* @author Alexander Preuß
|
||||
*/
|
||||
public class MethodPulsarListenerEndpoint<V> extends AbstractPulsarListenerEndpoint<V> {
|
||||
|
||||
@@ -143,7 +148,15 @@ public class MethodPulsarListenerEndpoint<V> extends AbstractPulsarListenerEndpo
|
||||
case LOCAL_DATE_TIME -> pulsarContainerProperties.setSchema(Schema.LOCAL_DATE_TIME);
|
||||
case LOCAL_TIME -> pulsarContainerProperties.setSchema(Schema.LOCAL_TIME);
|
||||
case JSON -> {
|
||||
final Schema<?> requiredSchema = getRequiredSchema(methodParameters[0], pulsarContainerProperties);
|
||||
final Schema<?> requiredSchema = getRequiredSchema(methodParameters[0], JSONSchema::of);
|
||||
pulsarContainerProperties.setSchema(requiredSchema);
|
||||
}
|
||||
case AVRO -> {
|
||||
final Schema<?> requiredSchema = getRequiredSchema(methodParameters[0], AvroSchema::of);
|
||||
pulsarContainerProperties.setSchema(requiredSchema);
|
||||
}
|
||||
case KEY_VALUE -> {
|
||||
final Schema<?> requiredSchema = getRequiredKeyValueSchema(methodParameters[0]);
|
||||
pulsarContainerProperties.setSchema(requiredSchema);
|
||||
}
|
||||
}
|
||||
@@ -188,15 +201,29 @@ public class MethodPulsarListenerEndpoint<V> extends AbstractPulsarListenerEndpo
|
||||
return messageListener;
|
||||
}
|
||||
|
||||
private Schema<?> getRequiredSchema(MethodParameter methodParameter,
|
||||
PulsarContainerProperties pulsarContainerProperties) {
|
||||
private Schema<?> getRequiredSchema(MethodParameter methodParameter, Function<Class<?>, Schema<?>> schemaFactory) {
|
||||
ResolvableType resolvableType = ResolvableType.forMethodParameter(methodParameter);
|
||||
final Class<?> rawClass = resolvableType.getRawClass();
|
||||
if (rawClass != null && isContainerType(rawClass)) {
|
||||
resolvableType = resolvableType.getGeneric(0);
|
||||
}
|
||||
final Class<?> rawClazz = resolvableType.getRawClass();
|
||||
return JSONSchema.of(rawClazz);
|
||||
|
||||
return schemaFactory.apply(rawClazz);
|
||||
}
|
||||
|
||||
private Schema<?> getRequiredKeyValueSchema(MethodParameter methodParameter) {
|
||||
ResolvableType resolvableType = ResolvableType.forMethodParameter(methodParameter);
|
||||
final Class<?> rawClass = resolvableType.getRawClass();
|
||||
if (rawClass != null && isContainerType(rawClass)) {
|
||||
resolvableType = resolvableType.getGeneric(0);
|
||||
}
|
||||
Class<?> generic1 = resolvableType.resolveGeneric(0);
|
||||
Class<?> generic2 = resolvableType.resolveGeneric(1);
|
||||
Schema<? extends Class<?>> schema1 = SchemaUtils.getSchema(generic1.getName());
|
||||
Schema<? extends Class<?>> schema2 = SchemaUtils.getSchema(generic2.getName());
|
||||
|
||||
return Schema.KeyValue(schema1, schema2, KeyValueEncodingType.INLINE);
|
||||
}
|
||||
|
||||
private boolean isContainerType(Class<?> rawClass) {
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.apache.pulsar.client.api.Schema;
|
||||
* Utility class for Pulsar schema inference.
|
||||
*
|
||||
* @author Soby Chacko
|
||||
* @author Alexander Preuß
|
||||
*/
|
||||
public final class SchemaUtils {
|
||||
|
||||
@@ -29,9 +30,13 @@ public final class SchemaUtils {
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> Schema<T> getSchema(T message) {
|
||||
final String clazzName = message.getClass().getName();
|
||||
return getSchema(clazzName);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> Schema<T> getSchema(String clazzName) {
|
||||
return switch (clazzName) {
|
||||
case "java.lang.String" -> (Schema<T>) Schema.STRING;
|
||||
case "[B" -> (Schema<T>) Schema.BYTES;
|
||||
|
||||
@@ -19,14 +19,22 @@ package org.springframework.pulsar.listener;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.pulsar.client.admin.PulsarAdmin;
|
||||
import org.apache.pulsar.client.api.PulsarClient;
|
||||
import org.apache.pulsar.client.api.Schema;
|
||||
import org.apache.pulsar.client.impl.schema.AvroSchema;
|
||||
import org.apache.pulsar.client.impl.schema.JSONSchema;
|
||||
import org.apache.pulsar.common.schema.KeyValue;
|
||||
import org.apache.pulsar.common.schema.KeyValueEncodingType;
|
||||
import org.apache.pulsar.common.schema.SchemaType;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -241,4 +249,130 @@ public class PulsarListenerTests extends AbstractContainerBaseTests {
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
@ContextConfiguration(classes = SchemaTestCases.SchemaTestConfig.class)
|
||||
class SchemaTestCases {
|
||||
|
||||
static CountDownLatch jsonLatch = new CountDownLatch(1);
|
||||
|
||||
static CountDownLatch avroLatch = new CountDownLatch(1);
|
||||
|
||||
static CountDownLatch keyvalueLatch = new CountDownLatch(1);
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
void jsonSchema() throws Exception {
|
||||
PulsarProducerFactory<User> pulsarProducerFactory = new DefaultPulsarProducerFactory<>(pulsarClient,
|
||||
Collections.emptyMap());
|
||||
PulsarTemplate<User> template = new PulsarTemplate<>(pulsarProducerFactory);
|
||||
template.setSchema(JSONSchema.of(User.class));
|
||||
template.send("json-topic", new User("Jason", 1));
|
||||
assertThat(jsonLatch.await(10, TimeUnit.SECONDS)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void avroSchema() throws Exception {
|
||||
PulsarProducerFactory<User> pulsarProducerFactory = new DefaultPulsarProducerFactory<>(pulsarClient,
|
||||
Collections.emptyMap());
|
||||
PulsarTemplate<User> template = new PulsarTemplate<>(pulsarProducerFactory);
|
||||
template.setSchema(AvroSchema.of(User.class));
|
||||
template.send("avro-topic", new User("Avi", 2));
|
||||
assertThat(avroLatch.await(10, TimeUnit.SECONDS)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void keyvalueSchema() throws Exception {
|
||||
PulsarProducerFactory<KeyValue<String, Integer>> pulsarProducerFactory = new DefaultPulsarProducerFactory<>(
|
||||
pulsarClient, Collections.emptyMap());
|
||||
PulsarTemplate<KeyValue<String, Integer>> template = new PulsarTemplate<>(pulsarProducerFactory);
|
||||
|
||||
Schema<KeyValue<String, Integer>> kvSchema = Schema.KeyValue(Schema.STRING, Schema.INT32,
|
||||
KeyValueEncodingType.INLINE);
|
||||
|
||||
template.setSchema(kvSchema);
|
||||
template.send("keyvalue-topic", new KeyValue<>("Kevin", 3));
|
||||
assertThat(keyvalueLatch.await(10, TimeUnit.SECONDS)).isTrue();
|
||||
}
|
||||
|
||||
@EnablePulsar
|
||||
@Configuration
|
||||
static class SchemaTestConfig {
|
||||
|
||||
@PulsarListener(id = "jsonListener", topics = "json-topic", subscriptionName = "subscription-4",
|
||||
schemaType = SchemaType.JSON, properties = { "subscriptionInitialPosition=Earliest" })
|
||||
void listenJson(User message) {
|
||||
jsonLatch.countDown();
|
||||
}
|
||||
|
||||
@PulsarListener(id = "avroListener", topics = "avro-topic", subscriptionName = "subscription-5",
|
||||
schemaType = SchemaType.AVRO, properties = { "subscriptionInitialPosition=Earliest" })
|
||||
void listenAvro(User message) {
|
||||
avroLatch.countDown();
|
||||
}
|
||||
|
||||
@PulsarListener(id = "keyvalueListener", topics = "keyvalue-topic", subscriptionName = "subscription-6",
|
||||
schemaType = SchemaType.KEY_VALUE, properties = { "subscriptionInitialPosition=Earliest" })
|
||||
void listenKeyvalue(KeyValue<String, Integer> message) {
|
||||
keyvalueLatch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
static class User {
|
||||
|
||||
private String name;
|
||||
|
||||
private int age;
|
||||
|
||||
User() {
|
||||
|
||||
}
|
||||
|
||||
User(String name, int age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public void setAge(int age) {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
User user = (User) o;
|
||||
return age == user.age && Objects.equals(name, user.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, age);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "User{" + "name='" + name + '\'' + ", age=" + age + '}';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user