From bf89a9d4163ce607beb8cc0dd1c1270b482d5e75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=96=AF=E7=8B=82=E7=9A=84=E7=8B=AE=E5=AD=90li?= <15040126243@163.com> Date: Fri, 18 Nov 2022 19:14:07 +0800 Subject: [PATCH] =?UTF-8?q?reset=20dubbo=203.1.2=20=3D>=203.1.0=20?= =?UTF-8?q?=E9=99=8D=E7=BA=A7=E5=A4=84=E7=90=86=20=E9=97=AE=E9=A2=98?= =?UTF-8?q?=E4=BE=9D=E6=97=A7=E5=AD=98=E5=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-common/ruoyi-common-alibaba-bom/pom.xml | 2 +- .../apache/dubbo/metadata/MetadataInfo.java | 838 ++++++++++++++++++ 2 files changed, 839 insertions(+), 1 deletion(-) create mode 100644 ruoyi-common/ruoyi-common-dubbo/src/main/java/org/apache/dubbo/metadata/MetadataInfo.java diff --git a/ruoyi-common/ruoyi-common-alibaba-bom/pom.xml b/ruoyi-common/ruoyi-common-alibaba-bom/pom.xml index 0277a918..843a2e9a 100644 --- a/ruoyi-common/ruoyi-common-alibaba-bom/pom.xml +++ b/ruoyi-common/ruoyi-common-alibaba-bom/pom.xml @@ -19,7 +19,7 @@ 1.5.2 2.1.2 2.0.4 - 3.1.2 + 3.1.0 1.0.11 diff --git a/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/apache/dubbo/metadata/MetadataInfo.java b/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/apache/dubbo/metadata/MetadataInfo.java new file mode 100644 index 00000000..68bc3c34 --- /dev/null +++ b/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/apache/dubbo/metadata/MetadataInfo.java @@ -0,0 +1,838 @@ +/* + * 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.dubbo.metadata; + +import org.apache.dubbo.common.ProtocolServiceKey; +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.ExtensionLoader; +import org.apache.dubbo.common.logger.Logger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.url.component.URLParam; +import org.apache.dubbo.common.utils.ArrayUtils; +import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.dubbo.common.utils.JsonUtils; +import org.apache.dubbo.common.utils.StringUtils; + +import java.beans.Transient; +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentNavigableMap; +import java.util.concurrent.ConcurrentSkipListMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; + +import static org.apache.dubbo.common.constants.CommonConstants.DOT_SEPARATOR; +import static org.apache.dubbo.common.constants.CommonConstants.GROUP_CHAR_SEPARATOR; +import static org.apache.dubbo.common.constants.CommonConstants.METHODS_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.TIMESTAMP_KEY; +import static org.apache.dubbo.metadata.RevisionResolver.EMPTY_REVISION; + +public class MetadataInfo implements Serializable { + public static final MetadataInfo EMPTY = new MetadataInfo(); + private static final Logger logger = LoggerFactory.getLogger(MetadataInfo.class); + + private String app; + // revision that will report to registry or remote meta center, must always update together with rawMetadataInfo, check {@link this#calAndGetRevision} + private volatile String revision; + // key format is '{group}/{interface name}:{version}:{protocol}' + private final Map services; + + /* used at runtime */ + private transient AtomicBoolean initiated = new AtomicBoolean(false); + // Json formatted metadata that will report to remote meta center, must always update together with revision, check {@link this#calAndGetRevision} + private transient volatile String rawMetadataInfo; + // key format is '{group}/{interface name}:{version}' + private transient Map> subscribedServices; + private transient final Map extendParams; + private transient final Map instanceParams; + protected transient volatile boolean updated = false; + private transient ConcurrentNavigableMap> subscribedServiceURLs; + private transient ConcurrentNavigableMap> exportedServiceURLs; + private transient ExtensionLoader loader; + + public MetadataInfo() { + this(null); + } + + public MetadataInfo(String app) { + this(app, null, null); + } + + public MetadataInfo(String app, String revision, Map services) { + this.app = app; + this.revision = revision; + this.services = services == null ? new ConcurrentHashMap<>() : services; + this.extendParams = new ConcurrentHashMap<>(); + this.instanceParams = new ConcurrentHashMap<>(); + } + + private MetadataInfo(String app, String revision, Map services, AtomicBoolean initiated, + Map extendParams, Map instanceParams, boolean updated, + ConcurrentNavigableMap> subscribedServiceURLs, + ConcurrentNavigableMap> exportedServiceURLs, + ExtensionLoader loader) { + this.app = app; + this.revision = revision; + this.services = new ConcurrentHashMap<>(services); + this.initiated = new AtomicBoolean(initiated.get()); + this.extendParams = new ConcurrentHashMap<>(extendParams); + this.instanceParams = new ConcurrentHashMap<>(instanceParams); + this.updated = updated; + this.subscribedServiceURLs = subscribedServiceURLs == null ? null : new ConcurrentSkipListMap<>(subscribedServiceURLs); + this.exportedServiceURLs = exportedServiceURLs == null ? null : new ConcurrentSkipListMap<>(exportedServiceURLs); + this.loader = loader; + } + + /** + * Initialize is needed when MetadataInfo is created from deserialization on the consumer side before being used for RPC call. + */ + public void init() { + if (!initiated.compareAndSet(false, true)) { + return; + } + if (CollectionUtils.isNotEmptyMap(services)) { + services.forEach((_k, serviceInfo) -> { + serviceInfo.init(); + // create duplicate serviceKey(without protocol)->serviceInfo mapping to support metadata search when protocol is not specified on consumer side. + if (subscribedServices == null) { + subscribedServices = new HashMap<>(); + } + Set serviceInfos = subscribedServices.computeIfAbsent(serviceInfo.getServiceKey(), _key -> new HashSet<>()); + serviceInfos.add(serviceInfo); + }); + } + } + + public synchronized void addService(URL url) { + // fixme, pass in application mode context during initialization of MetadataInfo. + if (this.loader == null) { + this.loader = url.getOrDefaultApplicationModel().getExtensionLoader(MetadataParamsFilter.class); + } + List filters = loader.getActivateExtension(url, "params-filter"); + // generate service level metadata + ServiceInfo serviceInfo = new ServiceInfo(url, filters); + this.services.put(serviceInfo.getMatchKey(), serviceInfo); + // extract common instance level params + extractInstanceParams(url, filters); + + if (exportedServiceURLs == null) { + exportedServiceURLs = new ConcurrentSkipListMap<>(); + } + addURL(exportedServiceURLs, url); + updated = true; + } + + public synchronized void removeService(URL url) { + if (url == null) { + return; + } + this.services.remove(url.getProtocolServiceKey()); + if (exportedServiceURLs != null) { + removeURL(exportedServiceURLs, url); + } + + updated = true; + } + + public String getRevision() { + return revision; + } + + /** + * Calculation of this instance's status like revision and modification of the same instance must be synchronized among different threads. + *

+ * Usage of this method is strictly restricted to certain points such as when during registration. Always try to use {@link this#getRevision()} instead. + */ + public synchronized String calAndGetRevision() { + if (revision != null && !updated) { + return revision; + } + + updated = false; + + if (CollectionUtils.isEmptyMap(services)) { + this.revision = EMPTY_REVISION; + } else { + StringBuilder sb = new StringBuilder(); + sb.append(app); + for (Map.Entry entry : new TreeMap<>(services).entrySet()) { + sb.append(entry.getValue().toDescString()); + } + String tempRevision = RevisionResolver.calRevision(sb.toString()); + if (!StringUtils.isEquals(this.revision, tempRevision)) { + if (logger.isInfoEnabled()) { + logger.info(String.format("metadata revision changed: %s -> %s, app: %s, services: %d", this.revision, tempRevision, this.app, this.services.size())); + } + this.revision = tempRevision; + this.rawMetadataInfo = JsonUtils.getJson().toJson(this); + } + } + return revision; + } + + public void setRevision(String revision) { + this.revision = revision; + } + + @Transient + public String getContent() { + return this.rawMetadataInfo; + } + + public String getApp() { + return app; + } + + public void setApp(String app) { + this.app = app; + } + + public Map getServices() { + return services; + } + + /** + * Get service info of an interface with specified group, version and protocol + * @param protocolServiceKey key is of format '{group}/{interface name}:{version}:{protocol}' + * @return the specific service info related to protocolServiceKey + */ + public ServiceInfo getServiceInfo(String protocolServiceKey) { + return services.get(protocolServiceKey); + } + + /** + * Get service infos of an interface with specified group, version. + * There may have several service infos of different protocols, this method will simply pick the first one. + * + * @param serviceKeyWithoutProtocol key is of format '{group}/{interface name}:{version}' + * @return the first service info related to serviceKey + */ + public ServiceInfo getNoProtocolServiceInfo(String serviceKeyWithoutProtocol) { + if (CollectionUtils.isEmptyMap(subscribedServices)) { + return null; + } + Set subServices = subscribedServices.get(serviceKeyWithoutProtocol); + if (CollectionUtils.isNotEmpty(subServices)) { + return subServices.iterator().next(); + } + return null; + } + + public ServiceInfo getValidServiceInfo(String serviceKey) { + ServiceInfo serviceInfo = getServiceInfo(serviceKey); + if (serviceInfo == null) { + serviceInfo = getNoProtocolServiceInfo(serviceKey); + if (serviceInfo == null) { + return null; + } + } + return serviceInfo; + } + + public List getMatchedServiceInfos(ProtocolServiceKey consumerProtocolServiceKey) { + return getServices().values() + .stream() + .filter(serviceInfo -> serviceInfo.matchProtocolServiceKey(consumerProtocolServiceKey)) + .collect(Collectors.toList()); + } + + public Map getExtendParams() { + return extendParams; + } + + public Map getInstanceParams() { + return instanceParams; + } + + public String getParameter(String key, String serviceKey) { + ServiceInfo serviceInfo = getValidServiceInfo(serviceKey); + if (serviceInfo == null) return null; + return serviceInfo.getParameter(key); + } + + public Map getParameters(String serviceKey) { + ServiceInfo serviceInfo = getValidServiceInfo(serviceKey); + if (serviceInfo == null) { + return Collections.emptyMap(); + } + return serviceInfo.getAllParams(); + } + + public String getServiceString(String protocolServiceKey) { + if (protocolServiceKey == null) { + return null; + } + + ServiceInfo serviceInfo = getValidServiceInfo(protocolServiceKey); + if (serviceInfo == null) { + return null; + } + return serviceInfo.toFullString(); + } + + public synchronized void addSubscribedURL(URL url) { + if (subscribedServiceURLs == null) { + subscribedServiceURLs = new ConcurrentSkipListMap<>(); + } + addURL(subscribedServiceURLs, url); + } + + public boolean removeSubscribedURL(URL url) { + if (subscribedServiceURLs == null) { + return true; + } + return removeURL(subscribedServiceURLs, url); + } + + public ConcurrentNavigableMap> getSubscribedServiceURLs() { + return subscribedServiceURLs; + } + + public ConcurrentNavigableMap> getExportedServiceURLs() { + return exportedServiceURLs; + } + + private boolean addURL(Map> serviceURLs, URL url) { + SortedSet urls = serviceURLs.computeIfAbsent(url.getServiceKey(), this::newSortedURLs); + // make sure the parameters of tmpUrl is variable + return urls.add(url); + } + + boolean removeURL(Map> serviceURLs, URL url) { + String key = url.getServiceKey(); + SortedSet urls = serviceURLs.getOrDefault(key, null); + if (urls == null) { + return true; + } + boolean r = urls.remove(url); + // if it is empty + if (urls.isEmpty()) { + serviceURLs.remove(key); + } + return r; + } + + private SortedSet newSortedURLs(String serviceKey) { + return new TreeSet<>(URLComparator.INSTANCE); + } + + @Override + public int hashCode() { + return Objects.hash(app, services); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + + if (!(obj instanceof MetadataInfo)) { + return false; + } + + MetadataInfo other = (MetadataInfo)obj; + + return Objects.equals(app, other.getApp()) + && ((services == null && other.services == null) + || (services != null && services.equals(other.services))); + } + + private void extractInstanceParams(URL url, List filters) { + if (CollectionUtils.isEmpty(filters)) { + return; + } + + String[] included, excluded; + if (filters.size() == 1) { + MetadataParamsFilter filter = filters.get(0); + included = filter.instanceParamsIncluded(); + excluded = filter.instanceParamsExcluded(); + } else { + Set includedList = new HashSet<>(); + Set excludedList = new HashSet<>(); + filters.forEach(filter -> { + if (ArrayUtils.isNotEmpty(filter.instanceParamsIncluded())) { + includedList.addAll(Arrays.asList(filter.instanceParamsIncluded())); + } + if (ArrayUtils.isNotEmpty(filter.instanceParamsExcluded())) { + excludedList.addAll(Arrays.asList(filter.instanceParamsExcluded())); + } + }); + included = includedList.toArray(new String[0]); + excluded = excludedList.toArray(new String[0]); + } + + Map tmpInstanceParams = new HashMap<>(); + if (ArrayUtils.isNotEmpty(included)) { + for (String p : included) { + String value = url.getParameter(p); + if (value != null) { + tmpInstanceParams.put(p, value); + } + } + } else if (ArrayUtils.isNotEmpty(excluded)) { + tmpInstanceParams.putAll(url.getParameters()); + for (String p : excluded) { + tmpInstanceParams.remove(p); + } + } + + tmpInstanceParams.forEach((key, value) -> { + String oldValue = instanceParams.put(key, value); + if (!TIMESTAMP_KEY.equals(key) && oldValue != null && !oldValue.equals(value)) { + throw new IllegalStateException(String.format("Inconsistent instance metadata found in different services: %s, %s", oldValue, value)); + } + }); + } + + @Override + public String toString() { + return "metadata{" + + "app='" + app + "'," + + "revision='" + revision + "'," + + "size=" + (services == null ? 0 : services.size()) + "," + + "services=" + getSimplifiedServices(services) + + "}"; + } + + public String toFullString() { + return "metadata{" + + "app='" + app + "'," + + "revision='" + revision + "'," + + "services=" + services + + "}"; + } + + private String getSimplifiedServices(Map services) { + if (services == null) { + return "[]"; + } + + return services.keySet().toString(); + } + + @Override + public synchronized MetadataInfo clone() { + return new MetadataInfo(app, revision, services, initiated, extendParams, instanceParams, updated, subscribedServiceURLs, exportedServiceURLs, loader); + } + + private Object readResolve() { + // create a new object from the deserialized one, in order to call constructor + return new MetadataInfo(this.app, this.revision, this.services); + } + + public static class ServiceInfo implements Serializable { + private String name; + private String group; + private String version; + private String protocol; + private int port = -1; + private String path; // most of the time, path is the same with the interface name. + private Map params; + + // params configured on consumer side, + private volatile transient Map consumerParams; + // cached method params + private volatile transient Map> methodParams; + private volatile transient Map> consumerMethodParams; + // cached numbers + private volatile transient Map numbers; + private volatile transient Map> methodNumbers; + // service + group + version + private volatile transient String serviceKey; + // service + group + version + protocol + private volatile transient String matchKey; + + private volatile transient ProtocolServiceKey protocolServiceKey; + + private transient URL url; + + public ServiceInfo() {} + + public ServiceInfo(URL url, List filters) { + this(url.getServiceInterface(), url.getGroup(), url.getVersion(), url.getProtocol(), url.getPort(), url.getPath(), null); + this.url = url; + Map params = extractServiceParams(url, filters); + // initialize method params caches. + this.methodParams = URLParam.initMethodParameters(params); + this.consumerMethodParams = URLParam.initMethodParameters(consumerParams); + } + + public ServiceInfo(String name, String group, String version, String protocol, int port, String path, Map params) { + this.name = name; + this.group = group; + this.version = version; + this.protocol = protocol; + this.port = port; + this.path = path; + this.params = params == null ? new ConcurrentHashMap<>() : params; + + this.serviceKey = buildServiceKey(name, group, version); + this.matchKey = buildMatchKey(); + } + + private Map extractServiceParams(URL url, List filters) { + Map params = new HashMap<>(); + + if (CollectionUtils.isEmpty(filters)) { + params.putAll(url.getParameters()); + this.params = params; + return params; + } + + String[] included, excluded; + if (filters.size() == 1) { + included = filters.get(0).serviceParamsIncluded(); + excluded = filters.get(0).serviceParamsExcluded(); + } else { + Set includedList = new HashSet<>(); + Set excludedList = new HashSet<>(); + for (MetadataParamsFilter filter : filters) { + if (ArrayUtils.isNotEmpty(filter.serviceParamsIncluded())) { + includedList.addAll(Arrays.asList(filter.serviceParamsIncluded())); + } + if (ArrayUtils.isNotEmpty(filter.serviceParamsExcluded())) { + excludedList.addAll(Arrays.asList(filter.serviceParamsExcluded())); + } + } + included = includedList.toArray(new String[0]); + excluded = excludedList.toArray(new String[0]); + } + + if (ArrayUtils.isNotEmpty(included)) { + String[] methods = url.getParameter(METHODS_KEY, (String[]) null); + for (String p : included) { + String value = url.getParameter(p); + if (StringUtils.isNotEmpty(value) && params.get(p) == null) { + params.put(p, value); + } + appendMethodParams(url, params, methods, p); + } + } else if (ArrayUtils.isNotEmpty(excluded)) { + for (Map.Entry entry : url.getParameters().entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + boolean shouldAdd = true; + for (String excludeKey : excluded) { + if (key.equalsIgnoreCase(excludeKey) || key.contains("." + excludeKey)) { + shouldAdd = false; + break; + } + } + if (shouldAdd) { + params.put(key, value); + } + } + } + + this.params = params; + return params; + } + + private void appendMethodParams(URL url, Map params, String[] methods, String p) { + if (methods != null) { + for (String method : methods) { + String mValue = url.getMethodParameterStrict(method, p); + if (StringUtils.isNotEmpty(mValue)) { + params.put(method + DOT_SEPARATOR + p, mValue); + } + } + } + } + + /** + * Initialize necessary caches right after deserialization on the consumer side + */ + protected void init() { + buildMatchKey(); + buildServiceKey(name, group, version); + // init method params + this.methodParams = URLParam.initMethodParameters(params); + // Actually, consumer params is empty after deserialized on the consumer side, so no need to initialize. + // Check how InstanceAddressURL operates on consumer url for more detail. +// this.consumerMethodParams = URLParam.initMethodParameters(consumerParams); + // no need to init numbers for it's only for cache purpose + } + + public String getMatchKey() { + if (matchKey != null) { + return matchKey; + } + buildMatchKey(); + return matchKey; + } + + private String buildMatchKey() { + matchKey = getServiceKey(); + if (StringUtils.isNotEmpty(protocol)) { + matchKey = getServiceKey() + GROUP_CHAR_SEPARATOR + protocol; + } + return matchKey; + } + + public boolean matchProtocolServiceKey(ProtocolServiceKey protocolServiceKey) { + return ProtocolServiceKey.Matcher.isMatch(protocolServiceKey, getProtocolServiceKey()); + } + + public ProtocolServiceKey getProtocolServiceKey() { + if (protocolServiceKey != null) { + return protocolServiceKey; + } + protocolServiceKey = new ProtocolServiceKey(name, version, group, protocol); + return protocolServiceKey; + } + + private String buildServiceKey(String name, String group, String version) { + this.serviceKey = URL.buildKey(name, group, version); + return this.serviceKey; + } + + public String getServiceKey() { + if (serviceKey != null) { + return serviceKey; + } + buildServiceKey(name, group, version); + return serviceKey; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public String getProtocol() { + return protocol; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public Map getParams() { + if (params == null) { + return Collections.emptyMap(); + } + return params; + } + + public void setParams(Map params) { + this.params = params; + } + + @Transient + public Map getAllParams() { + if (consumerParams != null) { + Map allParams = new HashMap<>((int) ((params.size() + consumerParams.size()) / 0.75f + 1)); + allParams.putAll(params); + allParams.putAll(consumerParams); + return allParams; + } + return params; + } + + public String getParameter(String key) { + if (consumerParams != null) { + String value = consumerParams.get(key); + if (value != null) { + return value; + } + } + return params.get(key); + } + + public String getMethodParameter(String method, String key, String defaultValue) { + String value = getMethodParameter(method, key, consumerMethodParams); + if (value != null) { + return value; + } + value = getMethodParameter(method, key, methodParams); + return value == null ? defaultValue : value; + } + + private String getMethodParameter(String method, String key, Map> map) { + String value = null; + if (map == null) { + return value; + } + + Map keyMap = map.get(method); + if (keyMap != null) { + value = keyMap.get(key); + } + return value; + } + + public boolean hasMethodParameter(String method, String key) { + String value = this.getMethodParameter(method, key, (String) null); + return StringUtils.isNotEmpty(value); + } + + public boolean hasMethodParameter(String method) { + return (consumerMethodParams != null && consumerMethodParams.containsKey(method)) + || (methodParams != null && methodParams.containsKey(method)); + } + + public String toDescString() { + return this.getMatchKey() + port + path + new TreeMap<>(getParams()); + } + + public void addParameter(String key, String value) { + if (consumerParams != null) { + this.consumerParams.put(key, value); + } + // refresh method params + consumerMethodParams = URLParam.initMethodParameters(consumerParams); + } + + public void addParameterIfAbsent(String key, String value) { + if (consumerParams != null) { + this.consumerParams.putIfAbsent(key, value); + } + // refresh method params + consumerMethodParams = URLParam.initMethodParameters(consumerParams); + } + + public void addConsumerParams(Map params) { + // copy once for one service subscription + if (consumerParams == null) { + consumerParams = new ConcurrentHashMap<>(params); + // init method params + consumerMethodParams = URLParam.initMethodParameters(consumerParams); + } + } + + public Map getNumbers() { + // concurrent initialization is tolerant + if (numbers == null) { + numbers = new ConcurrentHashMap<>(); + } + return numbers; + } + + public Map> getMethodNumbers() { + if (methodNumbers == null) { // concurrent initialization is tolerant + methodNumbers = new ConcurrentHashMap<>(); + } + return methodNumbers; + } + + public URL getUrl() { + return url; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (!(obj instanceof ServiceInfo)) { + return false; + } + + ServiceInfo serviceInfo = (ServiceInfo) obj; + /** + * Equals to Objects.equals(this.getMatchKey(), serviceInfo.getMatchKey()), but match key will not get initialized + * on json deserialization. + */ + return Objects.equals(this.getVersion(), serviceInfo.getVersion()) + && Objects.equals(this.getGroup(), serviceInfo.getGroup()) + && Objects.equals(this.getName(), serviceInfo.getName()) + && Objects.equals(this.getProtocol(), serviceInfo.getProtocol()) + && Objects.equals(this.getPort(), serviceInfo.getPort()) + && this.getParams().equals(serviceInfo.getParams()); + } + + @Override + public int hashCode() { + return Objects.hash(getVersion(), getGroup(), getName(), getProtocol(), getPort(), getParams()); + } + + @Override + public String toString() { + return getMatchKey(); + } + + public String toFullString() { + return "service{" + + "name='" + name + "'," + + "group='" + group + "'," + + "version='" + version + "'," + + "protocol='" + protocol + "'," + + "port='" + port + "'," + + "params=" + params + "," + + "}"; + } + } + + static class URLComparator implements Comparator { + + public static final URLComparator INSTANCE = new URLComparator(); + + @Override + public int compare(URL o1, URL o2) { + return o1.toFullString().compareTo(o2.toFullString()); + } + } +}