budd/src/main/java/io/github/ehlxr/extension/ExtensionLoader.java

380 lines
15 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* The MIT License (MIT)
*
* Copyright © 2020 xrv <xrg@live.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package io.github.ehlxr.extension;
import io.netty.util.internal.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Pattern;
/**
* 加载和管理扩展(简化版 Dubbo SPI
*
* @author ehlxr
*/
@SuppressWarnings("unused")
public class ExtensionLoader<T> {
private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class);
private static final String EXTENSIONS_DIRECTORY = "META-INF/extensions/";
private static final String EXTENSIONS_INTERNAL_DIRECTORY = "META-INF/extensions/internal/";
private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*,+\\s*");
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>();
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();
private final Class<T> type;
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
private final Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<>();
private String defaultExtension;
private ExtensionLoader(Class<T> type) {
this.type = type;
}
/**
* {@link ExtensionLoader} 的工厂方法。
*
* @param type 扩展点接口类型
* @param <T> 扩展点类型
* @return {@link ExtensionLoader} 实例
* @throws IllegalArgumentException 参数为 <code>null</code>
* 或是扩展点接口上没有 {@link SPI} 注解。
*/
@SuppressWarnings("unchecked")
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
if (type == null) {
throw new IllegalArgumentException("SPI type == null");
}
if (!type.isInterface()) {
throw new IllegalArgumentException("SPI type(" + type.getName() + ") is not interface!");
}
if (!type.isAnnotationPresent(SPI.class)) {
throw new IllegalArgumentException("type(" + type.getName() +
") is not a extension, because WITHOUT @SPI Annotation!");
}
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
/**
* Get the String of Throwable, like the output of {@link Throwable#printStackTrace()}.
*
* @param throwable the input throwable.
*/
private static String throwable2String(Throwable throwable) {
StringWriter w = new StringWriter(1024);
try (PrintWriter p = new PrintWriter(w)) {
throwable.printStackTrace(p);
return w.toString();
}
}
public T getExtension(String name) {
if (StringUtil.isNullOrEmpty(name)) {
throw new IllegalArgumentException("SPI name == null");
}
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<>());
holder = cachedInstances.get(name);
}
Object instance = holder.get();
if (instance == null) {
synchronized (cachedInstances) {
instance = holder.get();
if (instance == null) {
instance = createExtension(name);
holder.set(instance);
}
}
}
//noinspection unchecked
return (T) instance;
}
/**
* 返回缺省的扩展。
*
* @throws IllegalStateException 指定的扩展没有设置缺省扩展点
*/
public T getDefaultExtension() {
loadExtensionClasses0();
if (null == defaultExtension || defaultExtension.length() == 0) {
throw new IllegalStateException("No default extension on extension " + type.getName());
}
return getExtension(defaultExtension);
}
/**
* 获取扩展点实现的所有扩展点名。
*
* @since 0.1.0
*/
public Set<String> getSupportedExtensions() {
Map<String, Class<?>> classes = getExtensionClasses();
return Collections.unmodifiableSet(new HashSet<>(classes.keySet()));
}
public String getExtensionName(Class<?> spi) {
getExtensionClasses();
return cachedNames.get(spi);
}
private T createExtension(String name) {
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findExtensionClassLoadException(name);
}
try {
//noinspection unchecked
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
//noinspection unchecked
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("SPI instance(name: " + name + ", class: " +
type + ") could not be instantiated: " + t.getMessage(), t);
}
}
private Map<String, Class<?>> getExtensionClasses() {
Map<String, Class<?>> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
loadExtensionClasses0();
classes = cachedClasses.get();
}
}
}
return classes;
}
private IllegalStateException findExtensionClassLoadException(String name) {
for (Map.Entry<String, IllegalStateException> entry : exceptions.entrySet()) {
if (entry.getKey().toLowerCase().contains(name.toLowerCase())) {
return entry.getValue();
}
}
int i = 1;
StringBuilder buf = new StringBuilder("No such extension " + type.getName() + " by name " + name);
for (Map.Entry<String, IllegalStateException> entry : exceptions.entrySet()) {
if (i == 1) {
buf.append(", possible causes: ");
}
buf.append("\r\n(");
buf.append(i++);
buf.append(") ");
buf.append(entry.getKey());
buf.append(":\r\n");
buf.append(throwable2String(entry.getValue()));
}
return new IllegalStateException(buf.toString());
}
private void loadExtensionClasses0() {
final SPI annotation = type.getAnnotation(SPI.class);
if (annotation != null) {
String value = annotation.value();
if ((value = value.trim()).length() > 0) {
String[] names = NAME_SEPARATOR.split(value);
if (names.length > 1) {
throw new IllegalStateException("more than 1 default extension name on extension " +
type.getName() + ": " + Arrays.toString(names));
}
if (names.length == 1 && names[0].trim().length() > 0) {
defaultExtension = names[0].trim();
}
}
}
Map<String, Class<?>> extensionClasses = new HashMap<>();
loadFile(extensionClasses, EXTENSIONS_DIRECTORY);
loadFile(extensionClasses, EXTENSIONS_INTERNAL_DIRECTORY);
cachedClasses.set(extensionClasses);
}
private void loadFile(Map<String, Class<?>> extensionClasses, String dir) {
String fileName = dir + type.getName();
try {
Enumeration<URL> urls;
ClassLoader classLoader = ExtensionLoader.class.getClassLoader();
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
if (urls != null) {
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
try (
BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))
) {
String line;
while ((line = reader.readLine()) != null) {
// delete comments
final int ci = line.indexOf('#');
if (ci >= 0) {
line = line.substring(0, ci);
}
line = line.trim();
if (line.length() == 0) {
continue;
}
try {
String name = null;
int i = line.indexOf('=');
if (i > 0) {
name = line.substring(0, i).trim();
line = line.substring(i + 1).trim();
}
if (line.length() > 0) {
Class<? extends T> clazz = Class.forName(line, true, classLoader).asSubclass(type);
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error when load extension class(interface: " +
type.getName() + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + "is not subtype of interface.");
}
if (name == null || name.length() == 0) {
// clazz: xxx.xxx.ZfyAPI
// type: xxx.xxx.API
// -> name: zfy
if (clazz.getSimpleName().length() > type.getSimpleName().length()
&& clazz.getSimpleName().endsWith(type.getSimpleName())) {
name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length()
- type.getSimpleName().length()).toLowerCase();
} else {
throw new IllegalStateException("No such extension name for the class "
+ clazz.getName() + " in the config " + url);
}
}
if (!cachedNames.containsKey(clazz)) {
cachedNames.put(clazz, name);
}
Class<?> c = extensionClasses.get(name);
if (c == null) {
extensionClasses.put(name, clazz);
} else if (c != clazz) {
throw new IllegalStateException("Duplicate extension "
+ type.getName() + " name " + name + " on " + c.getName()
+ " and " + clazz.getName());
}
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: "
+ type + ", class line: " + line + ") in " + url + ", cause: " + t.getMessage(), t);
exceptions.put(line, e);
}
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " +
type + ", class file: " + url + ") in " + url, t);
}
}
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " +
type + ", description file: " + fileName + ").", t);
}
}
@Override
public String toString() {
return this.getClass().getName() + "[" + type.getName() + "]";
}
/**
* Holds a value of type <code>T</code>.
*/
@SuppressWarnings("unused")
private static final class Holder<T> {
/**
* The value contained in the holder.
*/
private volatile T value;
/**
* Creates a new holder with a <code>null</code> value.
*/
Holder() {
}
/**
* Create a new holder with the specified value.
*
* @param value The value to be stored in the holder.
*/
public Holder(T value) {
this.value = value;
}
public T get() {
return value;
}
public void set(T value) {
this.value = value;
}
}
}