/*
* 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.networknt.registry.support;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import com.networknt.registry.URLParamType;
import com.networknt.exception.FrameworkException;
import com.networknt.registry.NotifyListener;
import com.networknt.registry.URL;
import com.networknt.status.Status;
import com.networknt.utility.ConcurrentHashSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* Failback registry
*
* @author fishermen
*/
public abstract class FailbackRegistry extends AbstractRegistry {
private final static Logger logger = LoggerFactory.getLogger(FailbackRegistry.class);
private static final String REGISTER_ERROR = "ERR10020";
private static final String UNREGISTER_ERROR = "ERR10021";
private static final String SUBSCRIBE_ERROR = "ERR10022";
private static final String UNSUBSCRIBE_ERROR = "ERR10023";
private Set<URL> failedRegistered = new ConcurrentHashSet<>();
private Set<URL> failedUnregistered = new ConcurrentHashSet<>();
private ConcurrentHashMap<URL, ConcurrentHashSet<NotifyListener>> failedSubscribed =
new ConcurrentHashMap<>();
private ConcurrentHashMap<URL, ConcurrentHashSet<NotifyListener>> failedUnsubscribed =
new ConcurrentHashMap<>();
private static ScheduledExecutorService retryExecutor = Executors.newScheduledThreadPool(1);
public FailbackRegistry(URL url) {
super(url);
long retryPeriod = url.getIntParameter(URLParamType.registryRetryPeriod.getName(), URLParamType.registryRetryPeriod.getIntValue());
retryExecutor.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
retry();
} catch (Exception e) {
logger.error(String.format("[%s] False when retry in failback registry", registryClassName), e);
}
}
}, retryPeriod, retryPeriod, TimeUnit.MILLISECONDS);
}
@Override
public void register(URL url) {
failedRegistered.remove(url);
failedUnregistered.remove(url);
try {
super.register(url);
} catch (Exception e) {
if (isCheckingUrls(getUrl(), url)) {
throw new FrameworkException(new Status(REGISTER_ERROR, registryClassName, url, getUrl()), e);
}
failedRegistered.add(url);
}
}
@Override
public void unregister(URL url) {
failedRegistered.remove(url);
failedUnregistered.remove(url);
try {
super.unregister(url);
} catch (Exception e) {
if (isCheckingUrls(getUrl(), url)) {
throw new FrameworkException(new Status(UNREGISTER_ERROR, registryClassName, url, getUrl()), e);
}
failedUnregistered.add(url);
}
}
@Override
public void subscribe(URL url, NotifyListener listener) {
removeForFailedSubAndUnsub(url, listener);
try {
super.subscribe(url, listener);
} catch (Exception e) {
List<URL> cachedUrls = getCachedUrls(url);
if (cachedUrls != null && cachedUrls.size() > 0) {
listener.notify(getUrl(), cachedUrls);
} else if (isCheckingUrls(getUrl(), url)) {
logger.error(String.format("[%s] false to subscribe %s from %s", registryClassName, url, getUrl()), e);
throw new FrameworkException(new Status(SUBSCRIBE_ERROR, registryClassName, url, getUrl()), e);
}
addToFailedMap(failedSubscribed, url, listener);
}
}
@Override
public void unsubscribe(URL url, NotifyListener listener) {
removeForFailedSubAndUnsub(url, listener);
try {
super.unsubscribe(url, listener);
} catch (Exception e) {
if (isCheckingUrls(getUrl(), url)) {
throw new FrameworkException(new Status(UNSUBSCRIBE_ERROR, registryClassName, url, getUrl()), e);
}
addToFailedMap(failedUnsubscribed, url, listener);
}
}
@Override
@SuppressWarnings("unchecked")
public List<URL> discover(URL url) {
try {
return super.discover(url);
} catch (Exception e) {
// If discover fails, return an empty list
logger.error(String.format("Failed to discover url:%s in registry (%s)", url, getUrl()), e);
return Collections.EMPTY_LIST;
}
}
private boolean isCheckingUrls(URL... urls) {
for (URL url : urls) {
if (!Boolean.parseBoolean(url.getParameter(URLParamType.check.getName(), URLParamType.check.getValue()))) {
return false;
}
}
return true;
}
private void removeForFailedSubAndUnsub(URL url, NotifyListener listener) {
Set<NotifyListener> listeners = failedSubscribed.get(url);
if (listeners != null) {
listeners.remove(listener);
}
listeners = failedUnsubscribed.get(url);
if (listeners != null) {
listeners.remove(listener);
}
}
private void addToFailedMap(ConcurrentHashMap<URL, ConcurrentHashSet<NotifyListener>> failedMap, URL url, NotifyListener listener) {
Set<NotifyListener> listeners = failedMap.get(url);
if (listeners == null) {
failedMap.putIfAbsent(url, new ConcurrentHashSet<>());
listeners = failedMap.get(url);
}
listeners.add(listener);
}
private void retry() {
if (!failedRegistered.isEmpty()) {
Set<URL> failed = new HashSet<>(failedRegistered);
logger.info("[{}] Retry register {}", registryClassName, failed);
try {
for (URL url : failed) {
super.register(url);
failedRegistered.remove(url);
}
} catch (Exception e) {
logger.error(String.format("[%s] Failed to retry register, retry later, failedRegistered.size=%s, cause=%s",
registryClassName, failedRegistered.size(), e.getMessage()), e);
}
}
if (!failedUnregistered.isEmpty()) {
Set<URL> failed = new HashSet<>(failedUnregistered);
logger.info("[{}] Retry unregister {}", registryClassName, failed);
try {
for (URL url : failed) {
super.unregister(url);
failedUnregistered.remove(url);
}
} catch (Exception e) {
logger.error(String.format("[%s] Failed to retry unregister, retry later, failedUnregistered.size=%s, cause=%s",
registryClassName, failedUnregistered.size(), e.getMessage()), e);
}
}
if (!failedSubscribed.isEmpty()) {
Map<URL, Set<NotifyListener>> failed = new HashMap<>(failedSubscribed);
for (Map.Entry<URL, Set<NotifyListener>> entry : new HashMap<>(failed).entrySet()) {
if (entry.getValue() == null || entry.getValue().size() == 0) {
failed.remove(entry.getKey());
}
}
if (failed.size() > 0) {
logger.info("[{}] Retry subscribe {}", registryClassName, failed);
try {
for (Map.Entry<URL, Set<NotifyListener>> entry : failed.entrySet()) {
URL url = entry.getKey();
Set<NotifyListener> listeners = entry.getValue();
for (NotifyListener listener : listeners) {
super.subscribe(url, listener);
listeners.remove(listener);
}
}
} catch (Exception e) {
logger.error(String.format("[%s] Failed to retry subscribe, retry later, failedSubscribed.size=%s, cause=%s",
registryClassName, failedSubscribed.size(), e.getMessage()), e);
}
}
}
if (!failedUnsubscribed.isEmpty()) {
Map<URL, Set<NotifyListener>> failed = new HashMap<>(failedUnsubscribed);
for (Map.Entry<URL, Set<NotifyListener>> entry : new HashMap<>(failed).entrySet()) {
if (entry.getValue() == null || entry.getValue().size() == 0) {
failed.remove(entry.getKey());
}
}
if (failed.size() > 0) {
logger.info("[{}] Retry unsubscribe {}", registryClassName, failed);
try {
for (Map.Entry<URL, Set<NotifyListener>> entry : failed.entrySet()) {
URL url = entry.getKey();
Set<NotifyListener> listeners = entry.getValue();
for (NotifyListener listener : listeners) {
super.unsubscribe(url, listener);
listeners.remove(listener);
}
}
} catch (Exception e) {
logger.error(String.format("[%s] Failed to retry unsubscribe, retry later, failedUnsubscribed.size=%s, cause=%s",
registryClassName, failedUnsubscribed.size(), e.getMessage()), e);
}
}
}
}
}