All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.streamnative.pulsar.handlers.kop.schemaregistry.utils.SubjectUtils Maven / Gradle / Ivy

There is a newer version: 4.0.1.1
Show newest version
/**
 * 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