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

org.apache.pulsar.common.naming.TopicName Maven / Gradle / Ivy

There is a newer version: 4.0.0-preview.1
Show newest version
/*
 * 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.common.naming;

import org.apache.pulsar.shade.com.google.common.base.Splitter;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
import org.apache.pulsar.shade.org.apache.commons.lang3.StringUtils;
import org.apache.pulsar.common.util.Codec;

/**
 * Encapsulate the parsing of the completeTopicName name.
 */
public class TopicName implements ServiceUnitId {

    public static final String PUBLIC_TENANT = "public";
    public static final String DEFAULT_NAMESPACE = "default";

    public static final String PARTITIONED_TOPIC_SUFFIX = "-partition-";

    private final String completeTopicName;

    private final TopicDomain domain;
    private final String tenant;
    private final String cluster;
    private final String namespacePortion;
    private final String localName;

    private final NamespaceName namespaceName;

    private final int partitionIndex;

    private static final ConcurrentHashMap cache = new ConcurrentHashMap<>();

    public static void clearIfReachedMaxCapacity(int maxCapacity) {
        if (maxCapacity < 0) {
            // Unlimited cache.
            return;
        }
        if (cache.size() > maxCapacity) {
            cache.clear();
        }
    }

    public static TopicName get(String domain, NamespaceName namespaceName, String topic) {
        String name = domain + "://" + namespaceName.toString() + '/' + topic;
        return TopicName.get(name);
    }

    public static TopicName get(String domain, String tenant, String namespace, String topic) {
        String name = domain + "://" + tenant + '/' + namespace + '/' + topic;
        return TopicName.get(name);
    }

    public static TopicName get(String domain, String tenant, String cluster, String namespace,
                                String topic) {
        String name = domain + "://" + tenant + '/' + cluster + '/' + namespace + '/' + topic;
        return TopicName.get(name);
    }

    public static TopicName get(String topic) {
        TopicName tp = cache.get(topic);
        if (tp != null) {
            return tp;
        }
        return cache.computeIfAbsent(topic, k -> new TopicName(k));
    }

    public static TopicName getPartitionedTopicName(String topic) {
        TopicName topicName = TopicName.get(topic);
        if (topicName.isPartitioned()) {
            return TopicName.get(topicName.getPartitionedTopicName());
        }
        return topicName;
    }

    public static boolean isValid(String topic) {
        try {
            get(topic);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    public static String getPartitionPattern(String topic) {
        return "^" + Pattern.quote(get(topic).getPartitionedTopicName().toString()) + "-partition-[0-9]+$";
    }

    public static String getPattern(String topic) {
        return "^" + Pattern.quote(get(topic).getPartitionedTopicName().toString()) + "$";
    }

    @SuppressFBWarnings("DCN_NULLPOINTER_EXCEPTION")
    private TopicName(String completeTopicName) {
        try {
            // The topic name can be in two different forms, one is fully qualified topic name,
            // the other one is short topic name
            if (!completeTopicName.contains("://")) {
                // The short topic name can be:
                // - 
                // - //
                String[] parts = StringUtils.split(completeTopicName, '/');
                if (parts.length == 3) {
                    completeTopicName = TopicDomain.persistent.name() + "://" + completeTopicName;
                } else if (parts.length == 1) {
                    completeTopicName = TopicDomain.persistent.name() + "://"
                        + PUBLIC_TENANT + "/" + DEFAULT_NAMESPACE + "/" + parts[0];
                } else {
                    throw new IllegalArgumentException(
                        "Invalid short topic name '" + completeTopicName + "', it should be in the format of "
                        + "// or ");
                }
            }

            // The fully qualified topic name can be in two different forms:
            // new:    persistent://tenant/namespace/topic
            // legacy: persistent://tenant/cluster/namespace/topic

            List parts = Splitter.on("://").limit(2).splitToList(completeTopicName);
            this.domain = TopicDomain.getEnum(parts.get(0));

            String rest = parts.get(1);

            // The rest of the name can be in different forms:
            // new:    tenant/namespace/
            // legacy: tenant/cluster/namespace/
            // Examples of localName:
            // 1. some, name, xyz
            // 2. xyz-123, feeder-2


            parts = Splitter.on("/").limit(4).splitToList(rest);
            if (parts.size() == 3) {
                // New topic name without cluster name
                this.tenant = parts.get(0);
                this.cluster = null;
                this.namespacePortion = parts.get(1);
                this.localName = parts.get(2);
                this.partitionIndex = getPartitionIndex(completeTopicName);
                this.namespaceName = NamespaceName.get(tenant, namespacePortion);
            } else if (parts.size() == 4) {
                // Legacy topic name that includes cluster name
                this.tenant = parts.get(0);
                this.cluster = parts.get(1);
                this.namespacePortion = parts.get(2);
                this.localName = parts.get(3);
                this.partitionIndex = getPartitionIndex(completeTopicName);
                this.namespaceName = NamespaceName.get(tenant, cluster, namespacePortion);
            } else {
                throw new IllegalArgumentException("Invalid topic name: " + completeTopicName);
            }


            if (localName == null || localName.isEmpty()) {
                throw new IllegalArgumentException("Invalid topic name: " + completeTopicName);
            }

        } catch (NullPointerException e) {
            throw new IllegalArgumentException("Invalid topic name: " + completeTopicName, e);
        }
        if (isV2()) {
            this.completeTopicName = String.format("%s://%s/%s/%s",
                                                   domain, tenant, namespacePortion, localName);
        } else {
            this.completeTopicName = String.format("%s://%s/%s/%s/%s",
                                                   domain, tenant, cluster,
                                                   namespacePortion, localName);
        }
    }

    public boolean isPersistent() {
        return TopicDomain.persistent == domain;
    }

    /**
     * Extract the namespace portion out of a completeTopicName name.
     *
     * 

Works both with old & new convention. * * @return the namespace */ public String getNamespace() { return namespaceName.toString(); } /** * Get the namespace object that this completeTopicName belongs to. * * @return namespace object */ @Override public NamespaceName getNamespaceObject() { return namespaceName; } public TopicDomain getDomain() { return domain; } public String getTenant() { return tenant; } @Deprecated public String getCluster() { return cluster; } public String getNamespacePortion() { return namespacePortion; } public String getLocalName() { return localName; } public String getEncodedLocalName() { return Codec.encode(localName); } public TopicName getPartition(int index) { if (index == -1 || this.toString().endsWith(PARTITIONED_TOPIC_SUFFIX + index)) { return this; } String partitionName = this.toString() + PARTITIONED_TOPIC_SUFFIX + index; return get(partitionName); } /** * @return partition index of the completeTopicName. * It returns -1 if the completeTopicName (topic) is not partitioned. */ public int getPartitionIndex() { return partitionIndex; } public boolean isPartitioned() { return partitionIndex != -1; } /** * For partitions in a topic, return the base partitioned topic name. * Eg: *

    *
  • persistent://prop/cluster/ns/my-topic-partition-1 --> * persistent://prop/cluster/ns/my-topic *
  • persistent://prop/cluster/ns/my-topic --> persistent://prop/cluster/ns/my-topic *
*/ public String getPartitionedTopicName() { if (isPartitioned()) { return completeTopicName.substring(0, completeTopicName.lastIndexOf("-partition-")); } else { return completeTopicName; } } /** * @return partition index of the completeTopicName. * It returns -1 if the completeTopicName (topic) is not partitioned. */ public static int getPartitionIndex(String topic) { int partitionIndex = -1; if (topic.contains(PARTITIONED_TOPIC_SUFFIX)) { try { String idx = StringUtils.substringAfterLast(topic, PARTITIONED_TOPIC_SUFFIX); partitionIndex = Integer.parseInt(idx); if (partitionIndex < 0) { // for the "topic-partition--1" partitionIndex = -1; } else if (StringUtils.length(idx) != String.valueOf(partitionIndex).length()) { // for the "topic-partition-01" partitionIndex = -1; } } catch (NumberFormatException nfe) { // ignore exception } } return partitionIndex; } /** * A helper method to get a partition name of a topic in String. * @return topic + "-partition-" + partition. */ public static String getTopicPartitionNameString(String topic, int partitionIndex) { return topic + PARTITIONED_TOPIC_SUFFIX + partitionIndex; } /** * Returns the http rest path for use in the admin web service. * Eg: * * "persistent/my-tenant/my-namespace/my-topic" * * "non-persistent/my-tenant/my-namespace/my-topic" * * @return topic rest path */ public String getRestPath() { return getRestPath(true); } public String getRestPath(boolean includeDomain) { String domainName = includeDomain ? domain + "/" : ""; if (isV2()) { return String.format("%s%s/%s/%s", domainName, tenant, namespacePortion, getEncodedLocalName()); } else { return String.format("%s%s/%s/%s/%s", domainName, tenant, cluster, namespacePortion, getEncodedLocalName()); } } /** * Returns the name of the persistence resource associated with the completeTopicName. * * @return the relative path to be used in persistence */ public String getPersistenceNamingEncoding() { // The convention is: domain://tenant/namespace/topic // We want to persist in the order: tenant/namespace/domain/topic // For legacy naming scheme, the convention is: domain://tenant/cluster/namespace/topic // We want to persist in the order: tenant/cluster/namespace/domain/topic if (isV2()) { return String.format("%s/%s/%s/%s", tenant, namespacePortion, domain, getEncodedLocalName()); } else { return String.format("%s/%s/%s/%s/%s", tenant, cluster, namespacePortion, domain, getEncodedLocalName()); } } /** * get topic full name from managedLedgerName. * * @return the topic full name, format -> domain://tenant/namespace/topic */ public static String fromPersistenceNamingEncoding(String mlName) { // The managedLedgerName convention is: tenant/namespace/domain/topic // We want to transform to topic full name in the order: domain://tenant/namespace/topic if (mlName == null || mlName.length() == 0) { return mlName; } List parts = Splitter.on("/").splitToList(mlName); String tenant; String cluster; String namespacePortion; String domain; String localName; if (parts.size() == 4) { tenant = parts.get(0); namespacePortion = parts.get(1); domain = parts.get(2); localName = Codec.decode(parts.get(3)); return String.format("%s://%s/%s/%s", domain, tenant, namespacePortion, localName); } else if (parts.size() == 5) { tenant = parts.get(0); cluster = parts.get(1); namespacePortion = parts.get(2); domain = parts.get(3); localName = Codec.decode(parts.get(4)); return String.format("%s://%s/%s/%s/%s", domain, tenant, cluster, namespacePortion, localName); } else { throw new IllegalArgumentException("Invalid managedLedger name: " + mlName); } } /** * Get a string suitable for completeTopicName lookup. * *

Example: * *

persistent://tenant/cluster/namespace/completeTopicName -> * persistent/tenant/cluster/namespace/completeTopicName * * @return */ public String getLookupName() { if (isV2()) { return String.format("%s/%s/%s/%s", domain, tenant, namespacePortion, getEncodedLocalName()); } else { return String.format("%s/%s/%s/%s/%s", domain, tenant, cluster, namespacePortion, getEncodedLocalName()); } } public boolean isGlobal() { return cluster == null || Constants.GLOBAL_CLUSTER.equalsIgnoreCase(cluster); } public String getSchemaName() { return getTenant() + "/" + getNamespacePortion() + "/" + TopicName.get(getPartitionedTopicName()).getEncodedLocalName(); } @Override public String toString() { return completeTopicName; } @Override public boolean equals(Object obj) { if (obj instanceof TopicName) { TopicName other = (TopicName) obj; return Objects.equals(completeTopicName, other.completeTopicName); } return false; } @Override public int hashCode() { return completeTopicName.hashCode(); } @Override public boolean includes(TopicName otherTopicName) { return this.equals(otherTopicName); } /** * Returns true if this a V2 topic name prop/ns/topic-name. * @return true if V2 */ public boolean isV2() { return cluster == null; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy