Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
*/
package org.apache.pulsar.client.api;

import static org.testng.Assert.fail;

import com.google.common.base.MoreObjects;
import com.google.common.collect.Sets;
import java.time.Clock;
Expand Down Expand Up @@ -459,7 +461,7 @@ public void testAvroProducerAndAutoSchemaConsumer() throws Exception {
}

Consumer<GenericRecord> consumer = pulsarClient
.newConsumer(Schema.AUTO())
.newConsumer(Schema.AUTO_CONSUME())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are these methods, and not fields? It looks weird.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made them methods to be consistent with AVRO and JSON.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AVRO and JSON take arguments though. A static method that takes no arguments is effectively a constant, and if it's not, it's doing something very suspicious in the background.

Anyhow, I wouldn't hold up the change for it, but I think it would be better as a constant.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually it can't be a constant. because it needs to set SchemaInfo when the client fetches schema info from brokers.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same applied to both AUTO_CONSUME() and AUTO_PRODUCE

.topic("persistent://my-property/use/my-ns/my-topic1")
.subscriptionName("my-subscriber-name")
.subscriptionInitialPosition(SubscriptionInitialPosition.Earliest)
Expand Down Expand Up @@ -507,7 +509,7 @@ public void testAvroProducerAndAutoSchemaReader() throws Exception {
}

Reader<GenericRecord> reader = pulsarClient
.newReader(Schema.AUTO())
.newReader(Schema.AUTO_CONSUME())
.topic("persistent://my-property/use/my-ns/my-topic1")
.startMessageId(MessageId.earliest)
.create();
Expand Down Expand Up @@ -535,4 +537,76 @@ public void testAvroProducerAndAutoSchemaReader() throws Exception {

}

@Test
public void testAutoBytesProducer() throws Exception {
log.info("-- Starting {} test --", methodName);

AvroSchema<AvroEncodedPojo> avroSchema =
AvroSchema.of(AvroEncodedPojo.class);

try (Producer<AvroEncodedPojo> producer = pulsarClient
.newProducer(avroSchema)
.topic("persistent://my-property/use/my-ns/my-topic1")
.create()) {
for (int i = 0; i < 10; i++) {
String message = "my-message-" + i;
producer.send(new AvroEncodedPojo(message));
}
}

try (Producer<byte[]> producer = pulsarClient
.newProducer(Schema.AUTO_PRODUCE_BYTES())
.topic("persistent://my-property/use/my-ns/my-topic1")
.create()) {
// try to produce junk data
for (int i = 10; i < 20; i++) {
String message = "my-message-" + i;
byte[] data = avroSchema.encode(new AvroEncodedPojo(message));
byte[] junkData = new byte[data.length / 2];
System.arraycopy(data, 0, junkData, 0, junkData.length);
try {
producer.send(junkData);
fail("Should fail on sending junk data");
} catch (SchemaSerializationException sse) {
// expected
}
}

for (int i = 10; i < 20; i++) {
String message = "my-message-" + i;
byte[] data = avroSchema.encode(new AvroEncodedPojo(message));
producer.send(data);
}
}

Consumer<GenericRecord> consumer = pulsarClient
.newConsumer(Schema.AUTO_CONSUME())
.topic("persistent://my-property/use/my-ns/my-topic1")
.subscriptionName("my-subscriber-name")
.subscriptionInitialPosition(SubscriptionInitialPosition.Earliest)
.subscribe();

Message<GenericRecord> msg = null;
Set<String> messageSet = Sets.newHashSet();
for (int i = 0; i < 20; i++) {
msg = consumer.receive(5, TimeUnit.SECONDS);
GenericRecord receivedMessage = msg.getValue();
log.debug("Received message: [{}]", receivedMessage);
String expectedMessage = "my-message-" + i;
String actualMessage = (String) receivedMessage.getField("message");
testMessageOrderAndDuplicates(messageSet, actualMessage, expectedMessage);
}
// Acknowledge the consumption of all messages at once
consumer.acknowledgeCumulative(msg);
consumer.close();

SchemaRegistry.SchemaAndMetadata storedSchema = pulsar.getSchemaRegistryService()
.getSchema("my-property/my-ns/my-topic1")
.get();

Assert.assertEquals(storedSchema.schema.getData(), avroSchema.getSchemaInfo().getSchema());

log.info("-- Exiting {} test --", methodName);

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,20 @@
package org.apache.pulsar.client.api;

import org.apache.pulsar.client.api.schema.GenericRecord;
import org.apache.pulsar.client.impl.schema.AutoSchema;
import org.apache.pulsar.client.impl.schema.AutoConsumeSchema;
import org.apache.pulsar.client.impl.schema.AutoProduceBytesSchema;
import org.apache.pulsar.client.impl.schema.AvroSchema;
import org.apache.pulsar.client.impl.schema.ByteSchema;
import org.apache.pulsar.client.impl.schema.BytesSchema;
import org.apache.pulsar.client.impl.schema.DoubleSchema;
import org.apache.pulsar.client.impl.schema.FloatSchema;
import org.apache.pulsar.client.impl.schema.IntSchema;
import org.apache.pulsar.client.impl.schema.JSONSchema;
import org.apache.pulsar.client.impl.schema.LongSchema;
import org.apache.pulsar.client.impl.schema.ProtobufSchema;
import org.apache.pulsar.client.impl.schema.ShortSchema;
import org.apache.pulsar.client.impl.schema.StringSchema;
import org.apache.pulsar.client.impl.schema.generic.GenericSchema;
import org.apache.pulsar.common.schema.SchemaInfo;

/**
Expand Down Expand Up @@ -80,7 +88,43 @@ static <T> Schema<T> JSON(Class<T> clazz) {
return JSONSchema.of(clazz);
}

@Deprecated
static Schema<GenericRecord> AUTO() {
return new AutoSchema();
return AUTO_CONSUME();
}

static Schema<GenericRecord> AUTO_CONSUME() {
return new AutoConsumeSchema();
}

static Schema<byte[]> AUTO_PRODUCE_BYTES() {
return new AutoProduceBytesSchema();
}

static Schema<?> getSchema(SchemaInfo schemaInfo) {
switch (schemaInfo.getType()) {
case INT8:
return ByteSchema.of();
case INT16:
return ShortSchema.of();
case INT32:
return IntSchema.of();
case INT64:
return LongSchema.of();
case STRING:
return StringSchema.utf8();
case FLOAT:
return FloatSchema.of();
case DOUBLE:
return DoubleSchema.of();
case BYTES:
return BytesSchema.of();
case JSON:
case AVRO:
return GenericSchema.of(schemaInfo);
default:
throw new IllegalArgumentException("Retrieve schema instance from schema info for type '"
+ schemaInfo.getType() + "' is not supported yet");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
/**
* Auto detect schema.
*/
public class AutoSchema implements Schema<GenericRecord> {
public class AutoConsumeSchema implements Schema<GenericRecord> {

private Schema<GenericRecord> schema;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
*
* http://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.apache.pulsar.client.impl.schema;


import static com.google.common.base.Preconditions.checkState;

import org.apache.pulsar.client.api.Schema;
import org.apache.pulsar.common.schema.SchemaInfo;

/**
* Auto detect schema.
*/
public class AutoProduceBytesSchema<T> implements Schema<byte[]> {

private Schema<T> schema;

public void setSchema(Schema<T> schema) {
this.schema = schema;
}

private void ensureSchemaInitialized() {
checkState(null != schema, "Schema is not initialized before used");
}

@Override
public byte[] encode(byte[] message) {
ensureSchemaInitialized();

// verify if the message can be decoded by the underlying schema
schema.decode(message);

return message;
}

@Override
public byte[] decode(byte[] bytes) {
ensureSchemaInitialized();

// verify the message can be detected by the underlying schema
schema.decode(bytes);

return bytes;
}

@Override
public SchemaInfo getSchemaInfo() {
ensureSchemaInitialized();

return schema.getSchemaInfo();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import lombok.extern.slf4j.Slf4j;
import org.apache.pulsar.client.api.Schema;
import org.apache.pulsar.client.api.schema.GenericRecord;
import org.apache.pulsar.client.impl.schema.AutoSchema;
import org.apache.pulsar.client.impl.schema.AutoConsumeSchema;
import org.apache.pulsar.client.schema.SchemaTestUtils.Bar;
import org.apache.pulsar.client.schema.SchemaTestUtils.Foo;
import org.testng.annotations.Test;
Expand Down Expand Up @@ -53,15 +53,15 @@ public void testGenericJsonSchema() {
@Test
public void testAutoAvroSchema() {
Schema<Foo> encodeSchema = Schema.AVRO(Foo.class);
AutoSchema decodeSchema = new AutoSchema();
AutoConsumeSchema decodeSchema = new AutoConsumeSchema();
decodeSchema.setSchema(GenericSchema.of(encodeSchema.getSchemaInfo()));
testEncodeAndDecodeGenericRecord(encodeSchema, decodeSchema);
}

@Test
public void testAutoJsonSchema() {
Schema<Foo> encodeSchema = Schema.JSON(Foo.class);
AutoSchema decodeSchema = new AutoSchema();
AutoConsumeSchema decodeSchema = new AutoConsumeSchema();
decodeSchema.setSchema(GenericSchema.of(encodeSchema.getSchemaInfo()));
testEncodeAndDecodeGenericRecord(encodeSchema, decodeSchema);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@
import org.apache.pulsar.client.impl.conf.ConsumerConfigurationData;
import org.apache.pulsar.client.impl.conf.ProducerConfigurationData;
import org.apache.pulsar.client.impl.conf.ReaderConfigurationData;
import org.apache.pulsar.client.impl.schema.AutoSchema;
import org.apache.pulsar.client.impl.schema.AutoConsumeSchema;
import org.apache.pulsar.client.impl.schema.AutoProduceBytesSchema;
import org.apache.pulsar.client.impl.schema.generic.GenericSchema;
import org.apache.pulsar.client.util.ExecutorProvider;
import org.apache.pulsar.common.api.proto.PulsarApi.CommandGetTopicsOfNamespace.Mode;
Expand Down Expand Up @@ -250,12 +251,12 @@ public <T> CompletableFuture<Producer<T>> createProducerAsync(ProducerConfigurat
ProducerInterceptors<T> interceptors) {
if (conf == null) {
return FutureUtil.failedFuture(
new PulsarClientException.InvalidConfigurationException("Producer configuration undefined"));
new PulsarClientException.InvalidConfigurationException("Producer configuration undefined"));
}

if (schema instanceof AutoSchema) {
if (schema instanceof AutoConsumeSchema) {
return FutureUtil.failedFuture(
new PulsarClientException.InvalidConfigurationException("AutoSchema is only used by consumers to detect schemas automatically"));
new PulsarClientException.InvalidConfigurationException("AutoConsumeSchema is only used by consumers to detect schemas automatically"));
}

if (state.get() != State.Open) {
Expand All @@ -266,9 +267,30 @@ public <T> CompletableFuture<Producer<T>> createProducerAsync(ProducerConfigurat

if (!TopicName.isValid(topic)) {
return FutureUtil.failedFuture(
new PulsarClientException.InvalidTopicNameException("Invalid topic name: '" + topic + "'"));
new PulsarClientException.InvalidTopicNameException("Invalid topic name: '" + topic + "'"));
}

if (schema instanceof AutoProduceBytesSchema) {
AutoProduceBytesSchema autoProduceBytesSchema = (AutoProduceBytesSchema) schema;
return lookup.getSchema(TopicName.get(conf.getTopicName()))
.thenCompose(schemaInfoOptional -> {
if (schemaInfoOptional.isPresent()) {
autoProduceBytesSchema.setSchema(Schema.getSchema(schemaInfoOptional.get()));
} else {
autoProduceBytesSchema.setSchema(Schema.BYTES);
}
return createProducerAsync(topic, conf, schema, interceptors);
});
} else {
return createProducerAsync(topic, conf, schema, interceptors);
}

}

private <T> CompletableFuture<Producer<T>> createProducerAsync(String topic,
ProducerConfigurationData conf,
Schema<T> schema,
ProducerInterceptors<T> interceptors) {
CompletableFuture<Producer<T>> producerCreatedFuture = new CompletableFuture<>();

getPartitionedTopicMetadata(topic).thenAccept(metadata -> {
Expand Down Expand Up @@ -392,15 +414,15 @@ public <T> CompletableFuture<Consumer<T>> subscribeAsync(ConsumerConfigurationDa
}

private <T> CompletableFuture<Consumer<T>> singleTopicSubscribeAsync(ConsumerConfigurationData<T> conf, Schema<T> schema, ConsumerInterceptors<T> interceptors) {
if (schema instanceof AutoSchema) {
AutoSchema autoSchema = (AutoSchema) schema;
if (schema instanceof AutoConsumeSchema) {
AutoConsumeSchema autoConsumeSchema = (AutoConsumeSchema) schema;
return lookup.getSchema(TopicName.get(conf.getSingleTopic()))
.thenCompose(schemaInfoOptional -> {
if (schemaInfoOptional.isPresent() && schemaInfoOptional.get().getType() == SchemaType.AVRO) {
GenericSchema genericSchema = GenericSchema.of(schemaInfoOptional.get());
log.info("Auto detected schema for topic {} : {}",
conf.getSingleTopic(), new String(schemaInfoOptional.get().getSchema(), UTF_8));
autoSchema.setSchema(genericSchema);
autoConsumeSchema.setSchema(genericSchema);
return doSingleTopicSubscribeAsync(conf, schema, interceptors);
} else {
return FutureUtil.failedFuture(
Expand Down Expand Up @@ -546,15 +568,15 @@ public CompletableFuture<Reader<byte[]>> createReaderAsync(ReaderConfigurationDa
}

public <T> CompletableFuture<Reader<T>> createReaderAsync(ReaderConfigurationData<T> conf, Schema<T> schema) {
if (schema instanceof AutoSchema) {
AutoSchema autoSchema = (AutoSchema) schema;
if (schema instanceof AutoConsumeSchema) {
AutoConsumeSchema autoConsumeSchema = (AutoConsumeSchema) schema;
return lookup.getSchema(TopicName.get(conf.getTopicName()))
.thenCompose(schemaInfoOptional -> {
if (schemaInfoOptional.isPresent() && schemaInfoOptional.get().getType() == SchemaType.AVRO) {
GenericSchema genericSchema = GenericSchema.of(schemaInfoOptional.get());
log.info("Auto detected schema for topic {} : {}",
conf.getTopicName(), new String(schemaInfoOptional.get().getSchema(), UTF_8));
autoSchema.setSchema(genericSchema);
autoConsumeSchema.setSchema(genericSchema);
return doCreateReaderAsync(conf, schema);
} else {
return FutureUtil.failedFuture(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,5 +85,16 @@ public enum SchemaType {
/**
* Auto Detect Schema Type.
*/
AUTO
@Deprecated
AUTO,

/**
* Auto Consume Type.
*/
AUTO_CONSUME,

/**
* Auto Publish Type.
*/
AUTO_PUBLISH
}
Loading