diff --git a/ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/cache/CacheStoreBuilder.java b/ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/cache/CacheStoreBuilder.java new file mode 100644 index 0000000..af82ad7 --- /dev/null +++ b/ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/cache/CacheStoreBuilder.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2020 LamGC + * + * ContentGrabbingJi is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * ContentGrabbingJi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.lamgc.cgj.bot.cache; + +import net.lamgc.cgj.bot.cache.convert.StringConverter; +import net.lamgc.cgj.bot.cache.exception.GetCacheStoreException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; +import java.util.function.Function; + +/** + * CacheStore 构造器. + * + *

这只是个门面类, 最终调用 {@link CacheStoreFactory} 的具体实现类获取 {@link CacheStore} 对象. + *

CacheStoreBuilder 确保了返回不为 null, + * 即使所有 Factory 无法返回合适的 CacheStore 实例, 也只会抛出 {@link NoSuchFactoryException} 异常. + * @see CacheStoreFactory + * @author LamGC + */ +public class CacheStoreBuilder { + + private final static Logger log = LoggerFactory.getLogger(CacheStoreBuilder.class); + private final static List FACTORY_LIST = new LinkedList<>(); + private final static Map FACTORY_INFO_MAP = new Hashtable<>(); + + /** + * 使用 SPI 机制加载所有缓存组件. + * + *

第一次执行时加载, 由 {@link #getFactory(Function)} 调用. + *

由于 ServiceLoader 线程不安全, 所以通过 synchronized 保证其安全性. + * 不通过 static 块进行初始化的原因是因为担心发生异常导致无法继续执行 + * (除非必要, 否则不要使用 static 执行可能会发生异常的代码.). + */ + private synchronized static void loadFactory() { + if (FACTORY_LIST.size() != 0) { + return; + } + final ServiceLoader FACTORY_LOADER = ServiceLoader.load(CacheStoreFactory.class); + try { + for (CacheStoreFactory factory : FACTORY_LOADER) { + FactoryInfo info; + try { + info = new FactoryInfo(factory.getClass()); + FACTORY_INFO_MAP.put(factory, info); + } catch (IllegalArgumentException e) { + log.warn("Factory {} 加载失败: {}", factory.getClass().getName(), e.getMessage()); + continue; + } + FACTORY_LIST.add(factory); + log.info("Factory {} 已加载(优先级: {}, 实现类: {}).", + info.getFactoryName(), + info.getFactoryPriority(), + factory.getClass().getName()); + } + FACTORY_LIST.sort(new PriorityComparator()); + } catch (Error error) { + // 防止发生 Error 又不输出到日志导致玄学问题难以排查. + log.error("加载 CacheStoreFactory 时发生严重错误.", error); + throw error; + } + } + + /** + * 优先级排序器. + */ + private final static class PriorityComparator implements Comparator { + @Override + public int compare(CacheStoreFactory o1, CacheStoreFactory o2) { + FactoryInfo info1 = Objects.requireNonNull(FACTORY_INFO_MAP.get(o1)); + FactoryInfo info2 = Objects.requireNonNull(FACTORY_INFO_MAP.get(o2)); + return info2.getFactoryPriority() - info1.getFactoryPriority(); + } + } + + /** + * 获取一个当前可用的高优先级 Factory 对象. + * @return 返回可用的高优先级 Factory 对象. + */ + private static > R getFactory(Function function) throws NoSuchFactoryException { + if (FACTORY_LIST.size() == 0) { + loadFactory(); + } + Iterator iterator = FACTORY_LIST.iterator(); + while (iterator.hasNext()) { + CacheStoreFactory factory = iterator.next(); + FactoryInfo info = FACTORY_INFO_MAP.get(factory); + try { + if (factory.canGetCacheStore()) { + log.debug("CacheStoreFactory {} 可用(优先级: {}).", info.getFactoryName(), info.getFactoryPriority()); + } + } catch (Exception e) { + log.error("CacheStoreFactory " + info.getFactoryName() + + " (" + factory.getClass().getName() + ") 检查可用性时发生异常", e); + continue; + } + + try { + R result = function.apply(factory); + log.trace("已通过 Factory '{}' 获取 CacheStore '{}'. (Factory实现类: {}).", + info.getFactoryName(), + result.getClass().getName(), + factory.getClass().getName()); + return result; + } catch (Exception e) { + if (!iterator.hasNext()) { + throw new NoSuchFactoryException(new GetCacheStoreException("CacheStoreFactory " + info.getFactoryName() + + " (" + factory.getClass().getName() + ") 创建 CacheStore 时发生异常.", e)); + } else { + if (e instanceof GetCacheStoreException) { + log.warn("CacheStoreFactory '{} ({})' 无法提供相应 CacheStore. 原因: {}", + info.getFactoryName(), factory.getClass().getName(), e.getMessage()); + } else { + log.warn("CacheStoreFactory '" + info.getFactoryName() + + " (" + factory.getClass().getName() + ")' 创建 CacheStore 时发生异常.", e); + } + } + } + } + throw new NoSuchFactoryException(); + } + + /** + * 获取单项缓存存储容器. + * @param identify 缓存容器标识. + * @param converter 类型转换器. + * @param 值类型. + * @return 返回新的存储容器, 与其他容器互不干扰. + * @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出. + */ + public static SingleCacheStore newSingleCacheStore(String identify, StringConverter converter) { + try { + return getFactory(factory -> { + SingleCacheStore singleCacheStoreInstance = factory.newSingleCacheStore(identify, converter); + if (singleCacheStoreInstance == null) { + throw new GetCacheStoreException("Factory " + factory.getClass().getName() + " 返回 null."); + } + return singleCacheStoreInstance; + }); + } catch (NoSuchFactoryException e) { + throw new GetCacheStoreException("无可用的 Factory.", e); + } + } + + /** + * 获取列表缓存存储容器. + * @param identify 缓存容器标识. + * @param converter 类型转换器. + * @param 元素类型. + * @return 返回新的存储容器, 与其他容器互不干扰. + * @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出. + */ + public static ListCacheStore newListCacheStore(String identify, StringConverter converter) { + try { + return getFactory(factory -> { + ListCacheStore listCacheStoreInstance = factory.newListCacheStore(identify, converter); + if (listCacheStoreInstance == null) { + throw new GetCacheStoreException("Factory " + factory.getClass().getName() + " 返回 null."); + } + return listCacheStoreInstance; + }); + } catch (NoSuchFactoryException e) { + throw new GetCacheStoreException("无可用的 Factory.", e); + } + } + + /** + * 获取集合缓存存储容器. + * @param identify 缓存容器标识. + * @param converter 类型转换器. + * @param 元素类型. + * @return 返回新的存储容器, 与其他容器互不干扰. + * @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出. + */ + public static SetCacheStore newSetCacheStore(String identify, StringConverter converter) { + try { + return getFactory(factory -> { + SetCacheStore setCacheStoreInstance = factory.newSetCacheStore(identify, converter); + if (setCacheStoreInstance == null) { + throw new GetCacheStoreException("Factory " + factory.getClass().getName() + " 返回 null."); + } + return setCacheStoreInstance; + }); + } catch (NoSuchFactoryException e) { + throw new GetCacheStoreException("无可用的 Factory.", e); + } + } + + /** + * 获取映射表缓存存储容器. + * @param identify 缓存容器标识. + * @param converter 类型转换器. + * @param 值类型. + * @return 返回新的存储容器, 与其他容器互不干扰. + * @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出. + */ + public static MapCacheStore newMapCacheStore(String identify, StringConverter converter) { + try { + return getFactory(factory -> { + MapCacheStore mapCacheStoreInstance = factory.newMapCacheStore(identify, converter); + if (mapCacheStoreInstance == null) { + throw new GetCacheStoreException("Factory " + factory.getClass().getName() + " 返回 null."); + } + return mapCacheStoreInstance; + }); + } catch (NoSuchFactoryException e) { + throw new GetCacheStoreException("无可用的 Factory.", e); + } + } + +} diff --git a/ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/cache/FactoryInfo.java b/ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/cache/FactoryInfo.java new file mode 100644 index 0000000..5a2c72e --- /dev/null +++ b/ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/cache/FactoryInfo.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2020 LamGC + * + * ContentGrabbingJi is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * ContentGrabbingJi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.lamgc.cgj.bot.cache; + +import com.google.common.base.Strings; + +/** + * CacheStoreFactory 的标识信息. + * @author LamGC + */ +public class FactoryInfo { + + private final String factoryName; + private final int factoryPriority; + + public FactoryInfo(Class factoryClass) { + Factory factoryAnnotation = factoryClass.getAnnotation(Factory.class); + if (factoryAnnotation == null) { + throw new IllegalArgumentException("Annotation not found"); + } else if (Strings.isNullOrEmpty(factoryAnnotation.name())) { + throw new IllegalArgumentException("Factory name is empty"); + } + + this.factoryName = factoryAnnotation.name(); + int factoryPriority = factoryAnnotation.priority(); + if (factoryPriority > FactoryPriority.PRIORITY_HIGHEST) { + this.factoryPriority = FactoryPriority.PRIORITY_HIGHEST; + } else if (factoryPriority < FactoryPriority.PRIORITY_LOWEST) { + this.factoryPriority = FactoryPriority.PRIORITY_LOWEST; + } else { + this.factoryPriority = factoryPriority; + } + } + + /** + * 获取 Factory 声明的名称. + * @return 返回 Factory 名称. + */ + public String getFactoryName() { + return factoryName; + } + + /** + * 获取 Factory 优先级. + * @return 返回 Factory 的优先级. + */ + public int getFactoryPriority() { + return factoryPriority; + } +} diff --git a/ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/cache/NoSuchFactoryException.java b/ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/cache/NoSuchFactoryException.java new file mode 100644 index 0000000..4783d17 --- /dev/null +++ b/ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/cache/NoSuchFactoryException.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2020 LamGC + * + * ContentGrabbingJi is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * ContentGrabbingJi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.lamgc.cgj.bot.cache; + +/** + * 找不到 Factory 异常. + *

当尝试获取 {@link CacheStoreFactory} 失败时抛出. + * @see CacheStoreFactory + * @author LamGC + */ +public class NoSuchFactoryException extends Exception { + + public NoSuchFactoryException() { + super("Unable to get available factory"); + } + + public NoSuchFactoryException(Throwable cause) { + super("Unable to get available factory", cause); + } +} diff --git a/ContentGrabbingJi-core/src/test/java/net/lamgc/cgj/bot/cache/CacheStoreBuilderTest.java b/ContentGrabbingJi-core/src/test/java/net/lamgc/cgj/bot/cache/CacheStoreBuilderTest.java new file mode 100644 index 0000000..f515e52 --- /dev/null +++ b/ContentGrabbingJi-core/src/test/java/net/lamgc/cgj/bot/cache/CacheStoreBuilderTest.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2020 LamGC + * + * ContentGrabbingJi is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * ContentGrabbingJi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.lamgc.cgj.bot.cache; + +import net.lamgc.cgj.bot.cache.convert.StringConverter; +import net.lamgc.cgj.bot.cache.convert.StringToStringConverter; +import org.junit.Assert; +import org.junit.Test; + +/** + * @see CacheStoreBuilder + */ +public class CacheStoreBuilderTest { + + @Test + public void getCacheStoreTest() { + final String identify = "test"; + final StringConverter converter = new StringToStringConverter(); + + Assert.assertNotNull(CacheStoreBuilder.newSingleCacheStore(identify, converter)); + Assert.assertNotNull(CacheStoreBuilder.newListCacheStore(identify, converter)); + Assert.assertNotNull(CacheStoreBuilder.newMapCacheStore(identify, converter)); + Assert.assertNotNull(CacheStoreBuilder.newSetCacheStore(identify, converter)); + } + + +}