io.streamnative.pulsar.handlers.kop.schemaregistry.utils.SubjectUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of pulsar-kafka-schema-registry Show documentation
Show all versions of pulsar-kafka-schema-registry Show documentation
Kafka Compatible Schema Registry
/**
* Copyright (c) 2019 - 2024 StreamNative, Inc.. All Rights Reserved.
*/
/**
* 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
*
* 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 io.streamnative.pulsar.handlers.kop.schemaregistry.utils;
import io.streamnative.pulsar.handlers.kop.schemaregistry.exceptions.RestInvalidSubjectException;
import java.util.function.Consumer;
import org.apache.pulsar.common.naming.TopicName;
public class SubjectUtils {
private static final int MAX_NAME_LENGTH = 249;
/**
* Expand the subject name to full subject name.
*
* Examples, for a namespace prefix "public/default"
* "persistent://public/default/test" => "public/default/test"
* "public/default/test" => "public/default/test"
* "public/default" => throw RestInvalidSubjectException
* "tenant.ns.topic" => "tenant/ns/topic"
* "topic" => "public/default/topic"
* "user.topic" => "public/default/user.topic"
* "tenant.ns.topic" => "tenant/ns/topic"
* "tenant.ns.user.topic" => "tenant/ns/user.topic"
*
* @param subject subject name
* @param namespace default namespace
* @return full subject name
* @throws RestInvalidSubjectException if the subject is invalid
*/
public static String normalize(String subject, String namespace) {
if (subject == null) {
throw new RestInvalidSubjectException("null", "Subject cannot be null");
}
final var prefix = (namespace.isEmpty() ? "" : namespace + "/");
// '/' is an invalid character in Kafka topic name, in this case, treat it as a Pulsar topic name
if (subject.contains("/")) {
try {
TopicName topicName = TopicName.get(subject);
return topicName.getNamespace() + "/" + topicName.getLocalName();
} catch (Exception e) {
throw new RestInvalidSubjectException(subject, e.getMessage());
}
}
final int index1 = subject.indexOf('.');
if (index1 < 0) {
return prefix + subject;
}
final int index2 = subject.indexOf('.', index1 + 1);
if (index2 < 0) {
return prefix + subject;
}
// We don't allow Kafka clients to access tenant and namespace whose name has a dot character
final var tenant = subject.substring(0, index1);
final var ns = subject.substring(index1 + 1, index2);
final var shortTopic = subject.substring(index2 + 1);
validate(shortTopic);
return tenant + "/" + ns + "/" + shortTopic;
}
public static void validate(String topic) {
validate(topic, "Topic name", message -> {
throw new RestInvalidSubjectException(topic, message);
});
}
public static void validate(String name, String logPrefix, Consumer throwableConsumer) {
String reasonInvalid = detectInvalidTopic(name);
if (reasonInvalid != null) {
throwableConsumer.accept(logPrefix + " is invalid: " + reasonInvalid);
}
}
/**
* Copy from org.apache.kafka.common.internals.Topic.
*/
private static String detectInvalidTopic(String name) {
if (name.isEmpty()) {
return "the empty string is not allowed";
}
if (".".equals(name)) {
return "'.' is not allowed";
}
if ("..".equals(name)) {
return "'..' is not allowed";
}
if (name.length() > MAX_NAME_LENGTH){
return "the length of '" + name + "' is longer than the max allowed length " + MAX_NAME_LENGTH;
}
if (!containsValidPattern(name)){
return "'" + name + "' contains one or more characters other than "
+ "ASCII alphanumerics, '.', '_' and '-'";
}
return null;
}
/**
* Valid characters for Kafka topics are the ASCII alphanumerics, '.', '_', and '-'.
*/
static boolean containsValidPattern(String topic) {
for (int i = 0; i < topic.length(); ++i) {
char c = topic.charAt(i);
// We don't use Character.isLetterOrDigit(c) because it's slower
boolean validChar = (c >= 'a' && c <= 'z')
|| (c >= '0' && c <= '9')
|| (c >= 'A' && c <= 'Z')
|| c == '.'
|| c == '_'
|| c == '-';
if (!validChar) {
return false;
}
}
return true;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy