/*
* Copyright 2009-2016 Weibo, 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.weibo.api.motan.rpc;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import com.weibo.api.motan.common.MotanConstants;
import com.weibo.api.motan.common.URLParamType;
import com.weibo.api.motan.exception.MotanServiceException;
import com.weibo.api.motan.util.MotanFrameworkUtil;
/**
* <pre>
* Desc a reffer or a service.
* 所有获取URL的parameter时(即带参数的getXXX方法),都必须返回对象,避免不经意的修改引发错误,因为
* 有些地方需要根据是否含这个参数来进行操作。
*
* 对于getXXX,当不带defaultValue时,如果不存在就返回null
* </pre>
*
* @author fishermen
* @version V1.0 created at: 2013-5-16
*/
public class URL {
private String protocol;
private String host;
private int port;
// interfaceName
private String path;
private Map<String, String> parameters;
private volatile transient Map<String, Number> numbers;
public URL(String protocol, String host, int port, String path) {
this(protocol, host, port, path, new HashMap<String, String>());
}
public URL(String protocol, String host, int port, String path, Map<String, String> parameters) {
this.protocol = protocol;
this.host = host;
this.port = port;
this.path = removeAsyncPath(path);
this.parameters = parameters;
}
public static URL valueOf(String url) {
if (StringUtils.isBlank(url)) {
throw new MotanServiceException("url is null");
}
String protocol = null;
String host = null;
int port = 0;
String path = null;
Map<String, String> parameters = new HashMap<String, String>();;
int i = url.indexOf("?"); // seperator between body and parameters
if (i >= 0) {
String[] parts = url.substring(i + 1).split("\\&");
for (String part : parts) {
part = part.trim();
if (part.length() > 0) {
int j = part.indexOf('=');
if (j >= 0) {
parameters.put(part.substring(0, j), part.substring(j + 1));
} else {
parameters.put(part, part);
}
}
}
url = url.substring(0, i);
}
i = url.indexOf("://");
if (i >= 0) {
if (i == 0) throw new IllegalStateException("url missing protocol: \"" + url + "\"");
protocol = url.substring(0, i);
url = url.substring(i + 3);
} else {
i = url.indexOf(":/");
if (i >= 0) {
if (i == 0) throw new IllegalStateException("url missing protocol: \"" + url + "\"");
protocol = url.substring(0, i);
url = url.substring(i + 1);
}
}
i = url.indexOf("/");
if (i >= 0) {
path = url.substring(i + 1);
url = url.substring(0, i);
}
i = url.indexOf(":");
if (i >= 0 && i < url.length() - 1) {
port = Integer.parseInt(url.substring(i + 1));
url = url.substring(0, i);
}
if (url.length() > 0) host = url;
return new URL(protocol, host, port, path, parameters);
}
private static String buildHostPortStr(String host, int defaultPort) {
if (defaultPort <= 0) {
return host;
}
int idx = host.indexOf(":");
if (idx < 0) {
return host + ":" + defaultPort;
}
int port = Integer.parseInt(host.substring(idx + 1));
if (port <= 0) {
return host.substring(0, idx + 1) + defaultPort;
}
return host;
}
public URL createCopy() {
Map<String, String> params = new HashMap<String, String>();
if (this.parameters != null) {
params.putAll(this.parameters);
}
return new URL(protocol, host, port, path, params);
}
public String getProtocol() {
return protocol;
}
public void setProtocol(String protocol) {
this.protocol = protocol;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public Integer getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = removeAsyncPath(path);
}
public String getVersion() {
return getParameter(URLParamType.version.getName(), URLParamType.version.getValue());
}
public String getGroup() {
return getParameter(URLParamType.group.getName(), URLParamType.group.getValue());
}
public Map<String, String> getParameters() {
return parameters;
}
public String getParameter(String name) {
return parameters.get(name);
}
public String getParameter(String name, String defaultValue) {
String value = getParameter(name);
if (value == null) {
return defaultValue;
}
return value;
}
public String getMethodParameter(String methodName, String paramDesc, String name) {
String value = getParameter(MotanConstants.METHOD_CONFIG_PREFIX + methodName + "(" + paramDesc + ")." + name);
if (value == null || value.length() == 0) {
return getParameter(name);
}
return value;
}
public String getMethodParameter(String methodName, String paramDesc, String name, String defaultValue) {
String value = getMethodParameter(methodName, paramDesc, name);
if (value == null || value.length() == 0) {
return defaultValue;
}
return value;
}
public void addParameter(String name, String value) {
if (StringUtils.isEmpty(name) || StringUtils.isEmpty(value)) {
return;
}
parameters.put(name, value);
}
public void removeParameter(String name) {
if (name != null) {
parameters.remove(name);
}
}
public void addParameters(Map<String, String> params) {
parameters.putAll(params);
}
public void addParameterIfAbsent(String name, String value) {
if (hasParameter(name)) {
return;
}
parameters.put(name, value);
}
public Boolean getBooleanParameter(String name, boolean defaultValue) {
String value = getParameter(name);
if (value == null || value.length() == 0) {
return defaultValue;
}
return Boolean.parseBoolean(value);
}
public Boolean getMethodParameter(String methodName, String paramDesc, String name, boolean defaultValue) {
String value = getMethodParameter(methodName, paramDesc, name);
if (value == null || value.length() == 0) {
return defaultValue;
}
return Boolean.parseBoolean(value);
}
public Integer getIntParameter(String name, int defaultValue) {
Number n = getNumbers().get(name);
if (n != null) {
return n.intValue();
}
String value = parameters.get(name);
if (value == null || value.length() == 0) {
return defaultValue;
}
int i = Integer.parseInt(value);
getNumbers().put(name, i);
return i;
}
public Integer getMethodParameter(String methodName, String paramDesc, String name, int defaultValue) {
String key = methodName + "(" + paramDesc + ")." + name;
Number n = getNumbers().get(key);
if (n != null) {
return n.intValue();
}
String value = getMethodParameter(methodName, paramDesc, name);
if (value == null || value.length() == 0) {
return defaultValue;
}
int i = Integer.parseInt(value);
getNumbers().put(key, i);
return i;
}
public Long getLongParameter(String name, long defaultValue) {
Number n = getNumbers().get(name);
if (n != null) {
return n.longValue();
}
String value = parameters.get(name);
if (value == null || value.length() == 0) {
return defaultValue;
}
long l = Long.parseLong(value);
getNumbers().put(name, l);
return l;
}
public Long getMethodParameter(String methodName, String paramDesc, String name, long defaultValue) {
String key = methodName + "(" + paramDesc + ")." + name;
Number n = getNumbers().get(key);
if (n != null) {
return n.longValue();
}
String value = getMethodParameter(methodName, paramDesc, name);
if (value == null || value.length() == 0) {
return defaultValue;
}
long l = Long.parseLong(value);
getNumbers().put(key, l);
return l;
}
public Float getFloatParameter(String name, float defaultValue) {
Number n = getNumbers().get(name);
if (n != null) {
return n.floatValue();
}
String value = parameters.get(name);
if (value == null || value.length() == 0) {
return defaultValue;
}
float f = Float.parseFloat(value);
getNumbers().put(name, f);
return f;
}
public Float getMethodParameter(String methodName, String paramDesc, String name, float defaultValue) {
String key = methodName + "(" + paramDesc + ")." + name;
Number n = getNumbers().get(key);
if (n != null) {
return n.floatValue();
}
String value = getMethodParameter(methodName, paramDesc, name);
if (value == null || value.length() == 0) {
return defaultValue;
}
float f = Float.parseFloat(value);
getNumbers().put(key, f);
return f;
}
public Boolean getBooleanParameter(String name) {
String value = parameters.get(name);
if (value == null) {
return null;
}
return Boolean.parseBoolean(value);
}
public String getUri() {
return protocol + MotanConstants.PROTOCOL_SEPARATOR + host + ":" + port +
MotanConstants.PATH_SEPARATOR + path;
}
/**
* 返回一个service or referer的identity,如果两个url的identity相同,则表示相同的一个service或者referer
*
* @return
*/
public String getIdentity() {
return protocol + MotanConstants.PROTOCOL_SEPARATOR + host + ":" + port +
"/" + getParameter(URLParamType.group.getName(), URLParamType.group.getValue()) + "/" +
getPath() + "/" + getParameter(URLParamType.version.getName(), URLParamType.version.getValue()) +
"/" + getParameter(URLParamType.nodeType.getName(), URLParamType.nodeType.getValue());
}
/**
* check if this url can serve the refUrl.
*
* @param refUrl
* @return
*/
public boolean canServe(URL refUrl) {
if (refUrl == null || !this.getPath().equals(refUrl.getPath())) {
return false;
}
if (!ObjectUtils.equals(protocol, refUrl.protocol)) {
return false;
}
if (!StringUtils.equals(this.getParameter(URLParamType.nodeType.getName()), MotanConstants.NODE_TYPE_SERVICE)) {
return false;
}
String version = getParameter(URLParamType.version.getName(), URLParamType.version.getValue());
String refVersion = refUrl.getParameter(URLParamType.version.getName(), URLParamType.version.getValue());
if (!version.equals(refVersion)) {
return false;
}
// check serialize
String serialize = getParameter(URLParamType.serialize.getName(), URLParamType.serialize.getValue());
String refSerialize = refUrl.getParameter(URLParamType.serialize.getName(), URLParamType.serialize.getValue());
if (!serialize.equals(refSerialize)) {
return false;
}
// 由于需要提供跨group访问rpc的能力,所以不再验证group是否一致。
return true;
}
public String toFullStr() {
StringBuilder builder = new StringBuilder();
builder.append(getUri()).append("?");
for (Map.Entry<String, String> entry : parameters.entrySet()) {
String name = entry.getKey();
String value = entry.getValue();
builder.append(name).append("=").append(value).append("&");
}
return builder.toString();
}
public String toString() {
return toSimpleString();
}
// 包含协议、host、port、path、group
public String toSimpleString() {
return getUri() + "?group=" + getGroup();
}
public boolean hasParameter(String key) {
return StringUtils.isNotBlank(getParameter(key));
}
/**
* comma separated host:port pairs, e.g. "127.0.0.1:3000"
*
* @return
*/
public String getServerPortStr() {
return buildHostPortStr(host, port);
}
@Override
public int hashCode() {
int factor = 31;
int rs = 1;
rs = factor * rs + ObjectUtils.hashCode(protocol);
rs = factor * rs + ObjectUtils.hashCode(host);
rs = factor * rs + ObjectUtils.hashCode(port);
rs = factor * rs + ObjectUtils.hashCode(path);
rs = factor * rs + ObjectUtils.hashCode(parameters);
return rs;
}
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof URL)) {
return false;
}
URL ou = (URL) obj;
if (!ObjectUtils.equals(this.protocol, ou.protocol)) {
return false;
}
if (!ObjectUtils.equals(this.host, ou.host)) {
return false;
}
if (!ObjectUtils.equals(this.port, ou.port)) {
return false;
}
if (!ObjectUtils.equals(this.path, ou.path)) {
return false;
}
return ObjectUtils.equals(this.parameters, ou.parameters);
}
private Map<String, Number> getNumbers() {
if (numbers == null) { // 允许并发重复创建
numbers = new ConcurrentHashMap<String, Number>();
}
return numbers;
}
/**
* because async call in client path with Async suffix,we need
* remove Async suffix in path for subscribe.
* @param path
* @return
*/
private String removeAsyncPath(String path){
return MotanFrameworkUtil.removeAsyncSuffix(path);
}
}