org.apache.bookkeeper.common.net.ServiceURI Maven / Gradle / Ivy
/*
* 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.bookkeeper.common.net;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.base.CharMatcher;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.net.URI;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.bookkeeper.common.annotation.InterfaceAudience.Public;
import org.apache.bookkeeper.common.annotation.InterfaceStability.Evolving;
import org.apache.commons.lang.StringUtils;
/**
* ServiceURI represents service uri within bookkeeper cluster.
*
* Service URI syntax and components
*
* At the highest level a service uri is a {@link java.net.URI} in string
* form has the syntax.
*
*
* [service[service-specific-part]{@code :}[{@code //}authority][path]
*
*
* where the characters {@code :} and {@code /} stand for themselves.
*
*
The service-specific-part of a service URI consists of the backend information used for services to use.
* It has the syntax as below:
*
*
* [({@code -}|{@code +})][backend-part]
*
*
* where the characters {@code -} and {@code +} stand as a separator to separate service type
* from service backend information.
*
*
The authority component of a service URI has the same meaning as the authority component
* in a {@link java.net.URI}. If specified, it should be server-based. A server-based
* authority parses according to the familiar syntax
*
*
* [user-info{@code @}]host[{@code :}port]
*
*
* where the characters {@code @} and {@code :} stand for themselves.
*
*
The path component of a service URI is itself said to be absolute. It typically means which path a service
* stores metadata or data.
*
*
All told, then, a service URI instance has the following components:
*
*
*
* service {@code String}
* service-specific-part {@code String}
* authority {@code String}
* user-info {@code String}
* host {@code String}
* port {@code int}
* path {@code String}
*
*
*
* Some examples of service URIs are:
*
*
* {@code zk://localhost:2181/cluster1/ledgers} => ledger service uri using default ledger manager
* {@code zk+hierarchical://localhost:2181/ledgers} => ledger service uri using hierarchical ledger manager
* {@code etcd://localhost/ledgers} => ledger service uri using etcd as metadata store
* {@code distributedlog://localhost:2181/distributedlog} => distributedlog namespace
* {@code distributedlog-bk://localhost:2181/distributedlog} => distributedlog namespace with bk backend
* {@code bk://bookkeeper-cluster/} => stream storage service uri
* {@code host1:port,host2:port} => a list of hosts as bootstrap hosts to a stream storage cluster}
*
*
* @since 4.8.0
*/
@Public
@Evolving
public class ServiceURI {
/**
* Service string for ledger service that uses zookeeper as metadata store.
*/
public static final String SERVICE_ZK = "zk";
/**
* Service string for dlog service.
*/
public static final String SERVICE_DLOG = "distributedlog";
/**
* Service string for bookkeeper service.
*/
public static final String SERVICE_BK = "bk";
public static final int SERVICE_BK_PORT = 4181;
/**
* The default local bk service uri.
*/
public static final ServiceURI DEFAULT_LOCAL_STREAM_STORAGE_SERVICE_URI = ServiceURI.create("bk://localhost:4181");
private static final String SERVICE_SEP = "+";
private static final String SERVICE_DLOG_SEP = "-";
/**
* Create a service uri instance from a uri string.
*
* @param uriStr service uri string
* @return a service uri instance
* @throws NullPointerException if {@code uriStr} is null
* @throws IllegalArgumentException if the given string violates RFC 2396
*/
public static ServiceURI create(String uriStr) {
checkNotNull(uriStr, "service uri string is null");
// a service uri first should be a valid java.net.URI
URI uri = URI.create(uriStr);
return create(uri);
}
/**
* Create a service uri instance from a {@link URI} instance.
*
* @param uri {@link URI} instance
* @return a service uri instance
* @throws NullPointerException if {@code uriStr} is null
* @throws IllegalArgumentException if the given string violates RFC 2396
*/
public static ServiceURI create(URI uri) {
checkNotNull(uri, "service uri instance is null");
String serviceName;
String[] serviceInfos = new String[0];
String scheme = uri.getScheme();
if (null != scheme) {
scheme = scheme.toLowerCase();
final String serviceSep;
if (scheme.startsWith(SERVICE_DLOG)) {
serviceSep = SERVICE_DLOG_SEP;
} else {
serviceSep = SERVICE_SEP;
}
String[] schemeParts = StringUtils.split(scheme, serviceSep);
serviceName = schemeParts[0];
serviceInfos = new String[schemeParts.length - 1];
System.arraycopy(schemeParts, 1, serviceInfos, 0, serviceInfos.length);
} else {
serviceName = null;
}
String userAndHostInformation = uri.getAuthority();
checkArgument(!Strings.isNullOrEmpty(userAndHostInformation), "authority component is missing in service uri : " + uri);
String serviceUser;
List serviceHosts;
int atIndex = userAndHostInformation.indexOf('@');
Splitter splitter = Splitter.on(CharMatcher.anyOf(",;"));
if (atIndex > 0) {
serviceUser = userAndHostInformation.substring(0, atIndex);
serviceHosts = splitter.splitToList(userAndHostInformation.substring(atIndex + 1));
} else {
serviceUser = null;
serviceHosts = splitter.splitToList(userAndHostInformation);
}
serviceHosts = serviceHosts.stream().map(host -> validateHostName(serviceName, host)).collect(Collectors.toList());
String servicePath = uri.getPath();
checkArgument(null != servicePath, "service path component is missing in service uri : " + uri);
return new ServiceURI(serviceName, serviceInfos, serviceUser, serviceHosts.toArray(new String[serviceHosts.size()]), servicePath, uri);
}
private static String validateHostName(String serviceName, String hostname) {
String[] parts = hostname.split(":");
if (parts.length >= 3) {
throw new IllegalArgumentException("Invalid hostname : " + hostname);
} else if (parts.length == 2) {
try {
Integer.parseUnsignedInt(parts[1]);
} catch (NumberFormatException nfe) {
throw new IllegalArgumentException("Invalid hostname : " + hostname);
}
return hostname;
} else if (parts.length == 1 && serviceName.toLowerCase().equals(SERVICE_BK)) {
return hostname + ":" + SERVICE_BK_PORT;
} else {
return hostname;
}
}
private final String serviceName;
private final String[] serviceInfos;
private final String serviceUser;
private final String[] serviceHosts;
private final String servicePath;
private final URI uri;
@SuppressFBWarnings("EI_EXPOSE_REP")
public String[] getServiceInfos() {
return serviceInfos;
}
@SuppressFBWarnings("EI_EXPOSE_REP")
public String[] getServiceHosts() {
return serviceHosts;
}
private ServiceURI(final String serviceName, final String[] serviceInfos, final String serviceUser, final String[] serviceHosts, final String servicePath, final URI uri) {
this.serviceName = serviceName;
this.serviceInfos = serviceInfos;
this.serviceUser = serviceUser;
this.serviceHosts = serviceHosts;
this.servicePath = servicePath;
this.uri = uri;
}
public String getServiceName() {
return this.serviceName;
}
public String getServiceUser() {
return this.serviceUser;
}
public String getServicePath() {
return this.servicePath;
}
public URI getUri() {
return this.uri;
}
@Override
public boolean equals(final Object o) {
if (o == this) return true;
if (!(o instanceof ServiceURI)) return false;
final ServiceURI other = (ServiceURI) o;
if (!other.canEqual((Object) this)) return false;
final Object this$serviceName = this.getServiceName();
final Object other$serviceName = other.getServiceName();
if (this$serviceName == null ? other$serviceName != null : !this$serviceName.equals(other$serviceName)) return false;
if (!java.util.Arrays.deepEquals(this.getServiceInfos(), other.getServiceInfos())) return false;
final Object this$serviceUser = this.getServiceUser();
final Object other$serviceUser = other.getServiceUser();
if (this$serviceUser == null ? other$serviceUser != null : !this$serviceUser.equals(other$serviceUser)) return false;
if (!java.util.Arrays.deepEquals(this.getServiceHosts(), other.getServiceHosts())) return false;
final Object this$servicePath = this.getServicePath();
final Object other$servicePath = other.getServicePath();
if (this$servicePath == null ? other$servicePath != null : !this$servicePath.equals(other$servicePath)) return false;
final Object this$uri = this.getUri();
final Object other$uri = other.getUri();
if (this$uri == null ? other$uri != null : !this$uri.equals(other$uri)) return false;
return true;
}
protected boolean canEqual(final Object other) {
return other instanceof ServiceURI;
}
@Override
public int hashCode() {
final int PRIME = 59;
int result = 1;
final Object $serviceName = this.getServiceName();
result = result * PRIME + ($serviceName == null ? 43 : $serviceName.hashCode());
result = result * PRIME + java.util.Arrays.deepHashCode(this.getServiceInfos());
final Object $serviceUser = this.getServiceUser();
result = result * PRIME + ($serviceUser == null ? 43 : $serviceUser.hashCode());
result = result * PRIME + java.util.Arrays.deepHashCode(this.getServiceHosts());
final Object $servicePath = this.getServicePath();
result = result * PRIME + ($servicePath == null ? 43 : $servicePath.hashCode());
final Object $uri = this.getUri();
result = result * PRIME + ($uri == null ? 43 : $uri.hashCode());
return result;
}
}