/**
* Copyright 2016 Yahoo Inc.
*
* 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 com.yahoo.pulsar.common.naming;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.List;
import java.util.concurrent.ExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Objects;
import com.google.common.base.Splitter;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.UncheckedExecutionException;
import com.yahoo.pulsar.common.util.Codec;
/**
* Encapsulate the parsing of the destination name.
*/
public class DestinationName implements ServiceUnitId {
private static final Logger log = LoggerFactory.getLogger(DestinationName.class);
private static final String PARTITIONED_TOPIC_SUFFIX = "-partition-";
private final String destination;
private final DestinationDomain domain;
private final String property;
private final String cluster;
private final String namespacePortion;
private final String localName;
private final NamespaceName namespaceName;
private final int partitionIndex;
private static final LoadingCache<String, DestinationName> cache = CacheBuilder.newBuilder().maximumSize(100000)
.build(new CacheLoader<String, DestinationName>() {
@Override
public DestinationName load(String name) throws Exception {
return new DestinationName(name);
}
});
public static DestinationName get(String domain, String property, String cluster, String namespace,
String destination) {
String name = domain + "://" + property + '/' + cluster + '/' + namespace + '/' + destination;
return DestinationName.get(name);
}
public static DestinationName get(String destination) {
try {
return cache.get(destination);
} catch (ExecutionException e) {
throw (RuntimeException) e.getCause();
} catch (UncheckedExecutionException e) {
throw (RuntimeException) e.getCause();
}
}
public static boolean isValid(String destination) {
try {
get(destination);
return true;
} catch (Exception e) {
return false;
}
}
private DestinationName(String destination) {
this.destination = destination;
try {
// persistent://property/cluster/namespace/topic
if (!destination.contains("://")) {
throw new IllegalArgumentException(
"Invalid destination name: " + destination + " -- Domain is missing");
}
List<String> parts = Splitter.on("://").limit(2).splitToList(destination);
this.domain = DestinationDomain.valueOf(parts.get(0));
String rest = parts.get(1);
// property/cluster/namespace/<localName>
// Examples of localName:
// 1. some/name/xyz//
// 2. /xyz-123/feeder-2
parts = Splitter.on("/").limit(4).splitToList(rest);
if (parts.size() != 4) {
throw new IllegalArgumentException("Invalid destination name: " + destination);
}
this.property = parts.get(0);
this.cluster = parts.get(1);
this.namespacePortion = parts.get(2);
this.localName = parts.get(3);
this.partitionIndex = getPartitionIndex(destination);
NamespaceName.validateNamespaceName(property, cluster, namespacePortion);
if (checkNotNull(localName).isEmpty()) {
throw new IllegalArgumentException("Invalid destination name: " + destination);
}
} catch (NullPointerException e) {
throw new IllegalArgumentException("Invalid destination name: " + destination, e);
}
namespaceName = new NamespaceName(property, cluster, namespacePortion);
}
/**
* Extract the namespace portion out of a destination name.
*
* Works both with old & new convention.
*
* @return the namespace
*/
public String getNamespace() {
return namespaceName.toString();
}
/**
* Get the namespace object that this destination belongs to
*
* @return namespace object
*/
@Override
public NamespaceName getNamespaceObject() {
return namespaceName;
}
public DestinationDomain getDomain() {
return domain;
}
public String getProperty() {
return property;
}
public String getCluster() {
return cluster;
}
public String getNamespacePortion() {
return namespacePortion;
}
public String getLocalName() {
return localName;
}
public String getEncodedLocalName() {
return Codec.encode(localName);
}
public DestinationName getPartition(int index) {
if (this.toString().contains(PARTITIONED_TOPIC_SUFFIX)) {
return this;
}
String partitionName = this.toString() + PARTITIONED_TOPIC_SUFFIX + index;
return get(partitionName);
}
/**
* @return partition index of the destination. It returns -1 if the destination (topic) is not partitioned.
*/
public int getPartitionIndex() {
return partitionIndex;
}
/**
* @return partition index of the destination. It returns -1 if the destination (topic) is not partitioned.
*/
public static int getPartitionIndex(String topic) {
int partitionIndex = -1;
if (topic.contains(PARTITIONED_TOPIC_SUFFIX)) {
try {
partitionIndex = Integer.parseInt(topic.substring(topic.lastIndexOf('-') + 1));
} catch (NumberFormatException nfe) {
log.warn("Could not get the partition index from the topic {}", topic);
}
}
return partitionIndex;
}
/**
* Returns the name of the persistence resource associated with the destination.
*
* @return the relative path to be used in persistence
*/
public String getPersistenceNamingEncoding() {
// The convention is: domain://property/cluster/namespace/destination
// We want to persist in the order: property/cluster/namespace/domain/destination
return String.format("%s/%s/%s/%s/%s", property, cluster, namespacePortion, domain, getEncodedLocalName());
}
/**
* Get a string suitable for destination lookup
* <p>
* Example:
* <p>
* persistent://property/cluster/namespace/destination -> persistent/property/cluster/namespace/destination
*
* @return
*/
public String getLookupName() {
return String.format("%s/%s/%s/%s/%s", domain, property, cluster, namespacePortion, getEncodedLocalName());
}
public boolean isGlobal() {
return "global".equals(cluster);
}
@Override
public String toString() {
return destination;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof DestinationName) {
DestinationName other = (DestinationName) obj;
return Objects.equal(destination, other.destination);
}
return false;
}
@Override
public int hashCode() {
return destination.hashCode();
}
@Override
public boolean includes(DestinationName dn) {
return this.equals(dn);
}
}