/*
* 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.registry.support;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.commons.lang3.ObjectUtils;
import com.weibo.api.motan.common.MotanConstants;
import com.weibo.api.motan.common.URLParamType;
import com.weibo.api.motan.core.extension.SpiMeta;
import com.weibo.api.motan.registry.NotifyListener;
import com.weibo.api.motan.registry.RegistryService;
import com.weibo.api.motan.rpc.URL;
import com.weibo.api.motan.util.CollectionUtil;
import com.weibo.api.motan.util.ConcurrentHashSet;
import com.weibo.api.motan.util.LoggerUtil;
import com.weibo.api.motan.util.NetUtils;
/**
*
* @author maijunsheng
* @version 创建时间:2013-5-24
*
*/
@SpiMeta(name = "local")
public class LocalRegistryService extends AbstractRegistry {
/** Map<interface/nodeType, List<URL>>, List 中的url用identity/id来区分唯一性 */
private ConcurrentMap<String, List<URL>> registeredServices = new ConcurrentHashMap<String, List<URL>>();
private ConcurrentHashMap<String, ConcurrentHashMap<URL, ConcurrentHashSet<NotifyListener>>> subscribeListeners =
new ConcurrentHashMap<String, ConcurrentHashMap<URL, ConcurrentHashSet<NotifyListener>>>();
private URL registryUrl;
public LocalRegistryService() {
this(new URL(MotanConstants.REGISTRY_PROTOCOL_LOCAL, NetUtils.LOCALHOST, MotanConstants.DEFAULT_INT_VALUE,
RegistryService.class.getName()));
}
public LocalRegistryService(URL url) {
super(url);
this.registryUrl = url;
}
@Override
public void doSubscribe(URL url, NotifyListener listener) {
String subscribeKey = getSubscribeKey(url);
ConcurrentHashMap<URL, ConcurrentHashSet<NotifyListener>> urlListeners = subscribeListeners.get(subscribeKey);
if (urlListeners == null) {
subscribeListeners.putIfAbsent(subscribeKey, new ConcurrentHashMap<URL, ConcurrentHashSet<NotifyListener>>());
urlListeners = subscribeListeners.get(subscribeKey);
}
ConcurrentHashSet<NotifyListener> listeners = urlListeners.get(url);
if (listeners == null) {
urlListeners.putIfAbsent(url, new ConcurrentHashSet<NotifyListener>());
listeners = urlListeners.get(url);
}
listeners.add(listener);
List<URL> urls = discover(url);
if (urls != null && urls.size() > 0) {
listener.notify(getUrl(), urls);
}
LoggerUtil.info("LocalRegistryService subscribe: url={}", url);
}
@Override
public void doUnsubscribe(URL url, NotifyListener listener) {
String subscribeKey = getSubscribeKey(url);
ConcurrentHashMap<URL, ConcurrentHashSet<NotifyListener>> urlListeners = subscribeListeners.get(subscribeKey);
if (urlListeners != null) {
urlListeners.remove(url);
}
LoggerUtil.info("LocalRegistryService unsubscribe: url={}", url);
}
@Override
public List<URL> doDiscover(URL url) {
return registeredServices.get(getRegistryKey(url));
}
@Override
protected void doAvailable(URL url) {
//do nothing
}
@Override
protected void doUnavailable(URL url) {
//do nothing
}
@Override
public void doRegister(URL url) {
String registryKey = getRegistryKey(url);
synchronized (registeredServices) {
List<URL> urls = registeredServices.get(registryKey);
if (urls == null) {
registeredServices.putIfAbsent(registryKey, new ArrayList<URL>());
urls = registeredServices.get(registryKey);
}
add(url, urls);
LoggerUtil.info("LocalRegistryService register: url={}", url);
notifyListeners(url);
}
}
@Override
public void doUnregister(URL url) {
synchronized (registeredServices) {
List<URL> urls = registeredServices.get(getRegistryKey(url));
if (urls == null) {
return;
}
remove(url, urls);
LoggerUtil.info("LocalRegistryService unregister: url={}", url);
// 在变更后立即进行通知
notifyListeners(url);
}
}
@Override
public URL getUrl() {
return registryUrl;
}
/**
* 防止数据在外部被变更,因此copy一份
*
* @return
*/
public Map<String, List<URL>> getAllUrl() {
Map<String, List<URL>> copyMap = new HashMap<String, List<URL>>(registeredServices.size());
for (Map.Entry<String, List<URL>> entry : registeredServices.entrySet()) {
String key = entry.getKey();
List<URL> copyList = new ArrayList<URL>(entry.getValue().size());
for (URL url : entry.getValue()) {
copyList.add(url.createCopy());
}
copyMap.put(key, copyList);
}
return copyMap;
}
private void remove(URL url, List<URL> urls) {
if (CollectionUtil.isEmpty(urls)) {
return;
}
removeCachedUrlByIdentity(url, urls);
}
private void add(URL url, List<URL> urls) {
removeCachedUrlByIdentity(url, urls);
urls.add(url);
}
private void removeCachedUrlByIdentity(URL url, List<URL> urls) {
if (CollectionUtil.isEmpty(urls)) {
return;
}
URL oldUrl = null;
for (URL cachedUrl : urls) {
if (ObjectUtils.equals(url, cachedUrl)) {
oldUrl = cachedUrl;
break;
}
}
if (oldUrl != null) {
urls.remove(oldUrl);
}
}
private void notifyListeners(URL changedUrl) {
List<URL> interestingUrls = discover(changedUrl);
if (interestingUrls != null) {
ConcurrentHashMap<URL, ConcurrentHashSet<NotifyListener>> urlListeners = subscribeListeners.get(getSubscribeKey(changedUrl));
if (urlListeners == null) {
return;
}
for (ConcurrentHashSet<NotifyListener> listeners : urlListeners.values()) {
for (NotifyListener ln : listeners) {
try {
ln.notify(getUrl(), interestingUrls);
} catch (Exception e) {
LoggerUtil.warn(String.format("Exception when notify listerner %s, changedUrl: %s", ln, changedUrl), e);
}
}
}
}
}
private String getRegistryKey(URL url) {
String keyPrefix = url.getPath();
String nodeType = url.getParameter(URLParamType.nodeType.getName());
if (nodeType != null) {
return keyPrefix + MotanConstants.PATH_SEPARATOR + nodeType;
} else {
LoggerUtil.warn("Url need a nodeType as param in localRegistry, url={}", url);
return keyPrefix;
}
}
private String getSubscribeKey(URL url) {
return getRegistryKey(url);
}
}