package com.github.ltsopensource.core.spi;
import com.github.ltsopensource.core.cluster.Config;
import com.github.ltsopensource.core.commons.utils.Assert;
import com.github.ltsopensource.core.commons.utils.StringUtils;
import com.github.ltsopensource.core.logger.Logger;
import com.github.ltsopensource.core.logger.LoggerFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* @author Robert HG (254963746@qq.com)on 12/23/15.
*/
public class ServiceLoader {
private static final Logger LOGGER = LoggerFactory.getLogger(ServiceLoader.class);
private static final String LTS_DIRECTORY = "META-INF/lts/";
private static final String LTS_INTERNAL = "internal";
private static final String LTS_INTERNAL_DIRECTORY = LTS_DIRECTORY + LTS_INTERNAL + "/";
private static final String DEFAULT_IDENTITY = StringUtils.generateUUID();
private static final ConcurrentMap<Class<?>, ServiceProvider> serviceMap = new ConcurrentHashMap<Class<?>, ServiceProvider>();
private static final ConcurrentMap<IdentityUniqueKey, Object> cachedObjectMap = new ConcurrentHashMap<IdentityUniqueKey, Object>();
public static <T> T load(Class<T> clazz, Config config) {
ServiceProvider serviceProvider = getServiceProvider(clazz);
String dynamicServiceName = config.getParameter(serviceProvider.dynamicConfigKey);
String identity = config.getIdentity();
if(StringUtils.isEmpty(identity)){
throw new IllegalArgumentException("config.identity should not be null");
}
return load(clazz, dynamicServiceName, identity);
}
public static <T> T load(Class<T> clazz, Config config, String configKey) {
String dynamicServiceName = config.getParameter(configKey);
String identity = config.getIdentity();
if(StringUtils.isEmpty(identity)){
throw new IllegalArgumentException("config.identity should not be null");
}
return load(clazz, dynamicServiceName, identity);
}
public static <T> T loadDefault(Class<T> clazz) {
return load(clazz, "");
}
public static <T> T load(Class<T> clazz, String name) {
return load(clazz, name, DEFAULT_IDENTITY);
}
@SuppressWarnings("unchecked")
public static <T> T load(Class<T> clazz, String name, String identity) {
try {
ServiceProvider serviceProvider = getServiceProvider(clazz);
if (StringUtils.isEmpty(name)) {
// 加载默认的
name = serviceProvider.defaultName;
}
ServiceDefinition definition = serviceProvider.nameMaps.get(name);
if (definition == null) {
throw new IllegalStateException("Service loader could not load name:" + name + " class:" + clazz.getName() + "'s ServiceProvider from '" + LTS_DIRECTORY + "' or '" + LTS_INTERNAL_DIRECTORY + "' It may be empty or does not exist.");
}
// 用来保证每个节点都是一个各自的对象
IdentityUniqueKey uniqueKey = new IdentityUniqueKey(identity, definition);
Object obj = cachedObjectMap.get(uniqueKey);
if (obj != null) {
return (T) obj;
}
synchronized (definition) {
obj = cachedObjectMap.get(uniqueKey);
if (obj != null) {
return (T) obj;
}
String className = definition.clazz;
ClassLoader classLoader = definition.classLoader;
T srv = clazz.cast(ClassLoaderUtil.newInstance(classLoader, className));
cachedObjectMap.putIfAbsent(uniqueKey, srv);
return srv;
}
} catch (Exception e) {
throw new IllegalStateException("Service loader could not load name:" + name + " class:" + clazz.getName() + "'s ServiceProvider from '" + LTS_DIRECTORY + "' or '" + LTS_INTERNAL_DIRECTORY + "' It may be empty or does not exist.");
}
}
private static ServiceProvider getServiceProvider(Class<?> clazz) {
ServiceProvider serviceProvider = serviceMap.get(clazz);
if (serviceProvider == null) {
getServiceProviders(clazz);
serviceProvider = serviceMap.get(clazz);
}
return serviceProvider;
}
public static Set<String> getServiceProviders(final Class<?> clazz) {
if (clazz == null)
throw new IllegalArgumentException("type == null");
if (!clazz.isInterface()) {
throw new IllegalArgumentException(" type(" + clazz + ") is not interface!");
}
if (!clazz.isAnnotationPresent(SPI.class)) {
throw new IllegalArgumentException("type(" + clazz +
") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
}
SPI spi = clazz.getAnnotation(SPI.class);
String defaultName = spi.dftValue();
String dynamicConfigKey = spi.key();
final Set<URLDefinition> urlDefinitions = new HashSet<URLDefinition>();
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
urlDefinitions.addAll(collectExtensionUrls(LTS_DIRECTORY + clazz.getName(), classLoader));
urlDefinitions.addAll(collectExtensionUrls(LTS_INTERNAL_DIRECTORY + clazz.getName(), classLoader));
final ConcurrentMap<String, ServiceDefinition> serviceDefinitions = new ConcurrentHashMap<String, ServiceDefinition>();
for (URLDefinition urlDefinition : urlDefinitions) {
serviceDefinitions.putAll(parse(urlDefinition));
}
if (serviceDefinitions.isEmpty()) {
throw new IllegalStateException("Service loader could not load " + clazz.getName() + "'s ServiceProvider from '" + LTS_DIRECTORY + "' or '" + LTS_INTERNAL_DIRECTORY + "' It may be empty or does not exist.");
}
ServiceProvider serviceProvider = new ServiceProvider(clazz, dynamicConfigKey, defaultName, serviceDefinitions);
serviceMap.remove(clazz); // 先移除
serviceMap.put(clazz, serviceProvider);
return serviceDefinitions.keySet();
}
private static Map<String, ServiceDefinition> parse(URLDefinition urlDefinition) {
final Map<String, ServiceDefinition> nameClassMap = new HashMap<String, ServiceDefinition>();
try {
BufferedReader r = null;
try {
URL url = urlDefinition.uri.toURL();
r = new BufferedReader(new InputStreamReader(url.openStream(), "UTF-8"));
while (true) {
String line = r.readLine();
if (line == null) {
break;
}
int comment = line.indexOf('#');
if (comment >= 0) {
line = line.substring(0, comment);
}
line = line.trim();
if (line.length() == 0) {
continue;
}
int i = line.indexOf('=');
if (i > 0) {
String name = line.substring(0, i).trim();
String clazz = line.substring(i + 1).trim();
nameClassMap.put(name, new ServiceDefinition(name, clazz, urlDefinition.classLoader));
}
}
} finally {
if (r != null) {
r.close();
}
}
} catch (Exception e) {
LOGGER.error("parse " + urlDefinition.uri + " error:" + e.getMessage(), e);
}
return nameClassMap;
}
private static Set<URLDefinition> collectExtensionUrls(String resourceName, ClassLoader classLoader) {
try {
final Enumeration<URL> configs;
if (classLoader != null) {
configs = classLoader.getResources(resourceName);
} else {
configs = ClassLoader.getSystemResources(resourceName);
}
Set<URLDefinition> urlDefinitions = new HashSet<URLDefinition>();
while (configs.hasMoreElements()) {
URL url = configs.nextElement();
final URI uri = url.toURI();
ClassLoader highestClassLoader = findHighestReachableClassLoader(url, classLoader, resourceName);
urlDefinitions.add(new URLDefinition(uri, highestClassLoader));
}
return urlDefinitions;
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
return Collections.emptySet();
}
private static ClassLoader findHighestReachableClassLoader(URL url, ClassLoader classLoader, String resourceName) {
if (classLoader.getParent() == null) {
return classLoader;
}
ClassLoader highestClassLoader = classLoader;
ClassLoader current = classLoader;
while (current.getParent() != null) {
ClassLoader parent = current.getParent();
try {
Enumeration<URL> resources = parent.getResources(resourceName);
if (resources != null) {
while (resources.hasMoreElements()) {
URL resourceURL = resources.nextElement();
if (url.toURI().equals(resourceURL.toURI())) {
highestClassLoader = parent;
}
}
}
} catch (IOException ignore) {
} catch (URISyntaxException ignore) {
}
current = current.getParent();
}
return highestClassLoader;
}
private static final class URLDefinition {
private final URI uri;
private final ClassLoader classLoader;
private URLDefinition(URI url, ClassLoader classLoader) {
this.uri = url;
this.classLoader = classLoader;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
URLDefinition that = (URLDefinition) o;
return uri != null ? uri.equals(that.uri) : that.uri == null;
}
@Override
public int hashCode() {
return uri != null ? uri.hashCode() : 0;
}
}
private static final class ServiceDefinition {
private final String name;
private final String clazz;
private final ClassLoader classLoader;
private ServiceDefinition(String name, String clazz, ClassLoader classLoader) {
Assert.notNull(name, "name");
Assert.notNull(clazz, "clazz");
Assert.notNull(classLoader, "classLoader");
this.name = name;
this.clazz = clazz;
this.classLoader = classLoader;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ServiceDefinition that = (ServiceDefinition) o;
if (name != null ? !name.equals(that.name) : that.name != null) return false;
if (clazz != null ? !clazz.equals(that.clazz) : that.clazz != null) return false;
return classLoader != null ? classLoader.equals(that.classLoader) : that.classLoader == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + (clazz != null ? clazz.hashCode() : 0);
result = 31 * result + (classLoader != null ? classLoader.hashCode() : 0);
return result;
}
}
private static class IdentityUniqueKey {
private String identity;
private ServiceDefinition definition;
public IdentityUniqueKey(String identity, ServiceDefinition definition) {
this.identity = identity;
this.definition = definition;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
IdentityUniqueKey that = (IdentityUniqueKey) o;
if (identity != null ? !identity.equals(that.identity) : that.identity != null) return false;
return definition != null ? definition.equals(that.definition) : that.definition == null;
}
@Override
public int hashCode() {
int result = identity != null ? identity.hashCode() : 0;
result = 31 * result + (definition != null ? definition.hashCode() : 0);
return result;
}
}
private static final class ServiceProvider {
private final Class<?> clazz;
private final String defaultName;
private final String dynamicConfigKey;
private final ConcurrentMap<String, ServiceDefinition> nameMaps;
public ServiceProvider(Class<?> clazz, String dynamicConfigKey, String defaultName, ConcurrentMap<String, ServiceDefinition> nameMaps) {
this.clazz = clazz;
this.dynamicConfigKey = dynamicConfigKey;
this.defaultName = defaultName;
this.nameMaps = nameMaps;
}
}
}