diff --git a/designer-realize/src/main/java/com/fr/design/mainframe/app/DesignerAppUtils.java b/designer-realize/src/main/java/com/fr/design/mainframe/app/DesignerAppUtils.java index 4ac1a273a4..2064bc0449 100644 --- a/designer-realize/src/main/java/com/fr/design/mainframe/app/DesignerAppUtils.java +++ b/designer-realize/src/main/java/com/fr/design/mainframe/app/DesignerAppUtils.java @@ -7,16 +7,24 @@ import com.fr.design.extra.exe.callback.ModifyStatusCallback; import com.fr.design.i18n.Toolkit; import com.fr.design.mainframe.DesignerContext; import com.fr.design.ui.util.UIUtil; -import com.fr.io.TemplateIOErrorUtils; +import com.fr.locale.InterProviderFactory; import com.fr.plugin.context.PluginMarker; import com.fr.plugin.context.PluginMarkerAdapter; +import com.fr.plugin.engine.remote.PluginRemoteSync; import com.fr.plugin.manage.PluginManager; import com.fr.plugin.manage.control.PluginControllerHelper; import com.fr.plugin.manage.control.PluginTask; +import com.fr.stable.StringUtils; +import com.fr.stable.TemplateIOErrorContextHolder; +import com.fr.third.guava.cache.Cache; +import com.fr.third.guava.cache.CacheBuilder; import com.fr.third.guava.collect.Multimap; import com.fr.workspace.WorkContext; +import java.time.Duration; import java.util.Collection; +import java.util.Map; +import java.util.stream.Collectors; /** * 设计器app共用工具类 @@ -26,6 +34,118 @@ import java.util.Collection; * Created by vito on 2021/5/27 */ public class DesignerAppUtils { + private static final int DEFAULT_MAX_CACHE_SIZE = 50; + private static final int DEFAULT_CONCURRENCY_LEVEL = 8; + private static final long DEFAULT_EXPIRE_HOURS = 1; + + private static final Cache> ERROR_CACHE = CacheBuilder.newBuilder() + .maximumSize(DEFAULT_MAX_CACHE_SIZE) + .expireAfterAccess(Duration.ofHours(DEFAULT_EXPIRE_HOURS)) + .concurrencyLevel(DEFAULT_CONCURRENCY_LEVEL) + .build(); + + /** + * 弹出指定的插件信息, + * 并失效缓存 + * + * @param key 指定key + * @return 插件安装信息 + */ + public static Multimap popPluginInfoMap(String key) { + Multimap ifPresent = ERROR_CACHE.getIfPresent(key); + ERROR_CACHE.invalidate(key); + return ifPresent; + } + + /** + * 失效指定的插件信息缓存 + * + * @param key 指定key + */ + public static void invalidatePlugins(String key) { + ERROR_CACHE.invalidate(key); + } + + + /** + * 格式化多行插件错误信息详情并缓存, + * 用于界面展示 + * + * @param key 缓存key + * @return 格式化后的多行插件错误信息详情 + */ + public static String dealWithErrorDetailMultiLineAndCache(String key) { + Multimap pendingPlugins = TemplateIOErrorContextHolder.getPendingPlugin(); + if (pendingPlugins.isEmpty()) { + return StringUtils.EMPTY; + } + dealWithRemote(pendingPlugins); + StringBuilder sb = new StringBuilder(); + if (WorkContext.getCurrent().isLocal()) { + // 缓存等待后续处理 + ERROR_CACHE.put(key, pendingPlugins); + } + Collection unknownPlugins = pendingPlugins.get(TemplateIOErrorContextHolder.UNKNOWN_PLUGIN); + if (!unknownPlugins.isEmpty()) { + sb.append(InterProviderFactory.getProvider().getLocText("Fine-Core_Plugin_Error_UnknownPlugin")).append(":\n"); + for (PluginMarkerAdapter pluginMarker : unknownPlugins) { + sb.append("\"").append(pluginMarker.getPluginID()).append("\"") + .append(InterProviderFactory.getProvider().getLocText("Fine-Dec_Platform_Plugin")).append('\n'); + } + } + Collection notInstalledPlugins = pendingPlugins.get(TemplateIOErrorContextHolder.NOT_INSTALLED_PLUGIN); + if (!notInstalledPlugins.isEmpty()) { + sb.append(InterProviderFactory.getProvider().getLocText("Fine-Core_Plugin_Error_UninstalledPlugins")).append(":\n"); + for (PluginMarkerAdapter pluginMarker : notInstalledPlugins) { + sb.append("\"").append(pluginMarker.getPluginName()).append("\"") + .append(InterProviderFactory.getProvider().getLocText("Fine-Dec_Platform_Plugin")).append('\n'); + } + } + Collection disablePlugins = pendingPlugins.get(TemplateIOErrorContextHolder.DISABLE_PLUGIN); + if (!disablePlugins.isEmpty()) { + sb.append(InterProviderFactory.getProvider().getLocText("Fine-Core_Plugin_Error_InactivePlugins")).append(":\n"); + for (PluginMarkerAdapter pluginMarker : disablePlugins) { + sb.append("\"").append(pluginMarker.getPluginName()).append("\"") + .append(InterProviderFactory.getProvider().getLocText("Fine-Dec_Platform_Plugin")).append('\n'); + + } + } + return sb.toString(); + } + + /** + * 远程环境下需要特殊处理远程服务器尚未安装的插件 + * + * @param pendingPlugins 待处理插件 + */ + private static void dealWithRemote(Multimap pendingPlugins) { + if (!WorkContext.getCurrent().isLocal()) { + rearrange(pendingPlugins); + } + } + + /** + * 远程设计重新整理下列表 + * + * @param pendingPlugins 待处理列表 + */ + public static void rearrange(Multimap pendingPlugins) { + Map pluginRemoteStatusByIdIndex = PluginRemoteSync.getInstance().getPluginRemoteStatusByIdIndex(); + Collection unknownPlugins = pendingPlugins.get(TemplateIOErrorContextHolder.UNKNOWN_PLUGIN); + Collection notInstall = pendingPlugins.get(TemplateIOErrorContextHolder.NOT_INSTALLED_PLUGIN); + Collection disable = pendingPlugins.get(TemplateIOErrorContextHolder.DISABLE_PLUGIN); + unknownPlugins.removeIf(adapter -> pluginRemoteStatusByIdIndex.containsKey(adapter.getPluginID())); + // 本地未启用名单不准确添加到一起之后重新分配 + notInstall.addAll(disable); + disable.clear(); + // 从所有未安装中过滤远程未启用的,添加到未启用列表 + disable.addAll(notInstall.stream().filter(plugin -> + pluginRemoteStatusByIdIndex.containsKey(plugin.getPluginID()) + && !pluginRemoteStatusByIdIndex.get(plugin.getPluginID()).isRunning()) + .collect(Collectors.toList())); + // 清理未安装中所有远程安装过的插件(包含启用和未启用) + notInstall.removeIf(adapter -> pluginRemoteStatusByIdIndex.containsKey(adapter.getPluginID())); + } /** * 处理模板读取时的异常 @@ -34,7 +154,7 @@ public class DesignerAppUtils { */ public static void dealWithTemplateIOError(String path) { // 试图获取多行读取错误提示并缓存待处理列表 - String detail = TemplateIOErrorUtils.dealWithErrorDetailMultiLineAndCache(path); + String detail = dealWithErrorDetailMultiLineAndCache(path); if (detail.length() > 0) { UIUtil.invokeLaterIfNeeded(() -> { if (WorkContext.getCurrent().isLocal()) { @@ -52,7 +172,7 @@ public class DesignerAppUtils { @Override public void doCancel() { - TemplateIOErrorUtils.invalidatePlugins(path); + invalidatePlugins(path); } }).build().setVisible(true); } else { @@ -68,13 +188,13 @@ public class DesignerAppUtils { } private static void installAndEnablePlugin(String key) { - Multimap stringPluginMarkerAdapterMultimap = TemplateIOErrorUtils.popPluginInfoMap(key); - Collection disablePlugins = stringPluginMarkerAdapterMultimap.get(TemplateIOErrorUtils.DISABLE_PLUGIN); + Multimap stringPluginMarkerAdapterMultimap = popPluginInfoMap(key); + Collection disablePlugins = stringPluginMarkerAdapterMultimap.get(TemplateIOErrorContextHolder.DISABLE_PLUGIN); for (PluginMarkerAdapter disablePlugin : disablePlugins) { PluginManager.getController().enable(disablePlugin, new ModifyStatusCallback(false)); } - Collection uninstallPlugins = stringPluginMarkerAdapterMultimap.get(TemplateIOErrorUtils.NOT_INSTALLED_PLUGIN); + Collection uninstallPlugins = stringPluginMarkerAdapterMultimap.get(TemplateIOErrorContextHolder.NOT_INSTALLED_PLUGIN); for (PluginMarker uninstallPlugin : uninstallPlugins) { PluginTask pluginTask = PluginTask.installTask(uninstallPlugin); PluginControllerHelper.installOnline(uninstallPlugin, new InstallOnlineCallback(pluginTask)); diff --git a/designer-realize/src/main/java/com/fr/design/mainframe/app/FormApp.java b/designer-realize/src/main/java/com/fr/design/mainframe/app/FormApp.java index 511c98e62d..f28e2c9840 100644 --- a/designer-realize/src/main/java/com/fr/design/mainframe/app/FormApp.java +++ b/designer-realize/src/main/java/com/fr/design/mainframe/app/FormApp.java @@ -23,6 +23,7 @@ import com.fr.general.ComparatorUtils; import com.fr.log.FineLoggerFactory; import com.fr.stable.Constants; import com.fr.stable.bridge.StableFactory; +import org.jetbrains.annotations.Nullable; import java.util.HashMap; import java.util.concurrent.Callable; @@ -62,8 +63,7 @@ class FormApp extends AbstractAppProvider { new Callable>() { @Override public OpenResult call() throws Exception { - Form form = asIOFile(tplFile); - DesignerAppUtils.dealWithTemplateIOError(tplFile.getPath()); + Form form = getForm(tplFile); return new OpenResult<>(form, form.getParameters()); } }, emptyForm); @@ -72,22 +72,31 @@ class FormApp extends AbstractAppProvider { public JTemplate call() throws Exception { OpenResult result = worker.getResult(); return (JTemplate) StableFactory.getMarkedInstanceObjectFromClass(BaseJForm.XML_TAG, - new Object[]{result.getBaseBook(), tplFile, result.getRef()}, classType, BaseJForm.class); + new Object[]{result.getBaseBook(), tplFile, result.getRef()}, classType, BaseJForm.class); } }); worker.start(tplFile.getPath()); OpenResult result = worker.getResult(); if (result != null) { return (JTemplate) StableFactory.getMarkedInstanceObjectFromClass(BaseJForm.XML_TAG, - new Object[]{result.getBaseBook(), tplFile, new Parameter[0]}, classType, BaseJForm.class); + new Object[]{result.getBaseBook(), tplFile, new Parameter[0]}, classType, BaseJForm.class); } return emptyForm; } else { return (JTemplate) StableFactory.getMarkedInstanceObjectFromClass(BaseJForm.XML_TAG, - new Object[]{asIOFile(tplFile), tplFile}, classType, BaseJForm.class); + new Object[]{getForm(tplFile), tplFile}, classType, BaseJForm.class); } } + @Nullable + private Form getForm(FILE tplFile) { + Form form = asIOFile(tplFile); + if (form != null) { + DesignerAppUtils.dealWithTemplateIOError(tplFile.getPath()); + } + return form; + } + @Override public Form asIOFile(FILE file) { diff --git a/designer-realize/src/test/java/com/fr/design/mainframe/app/DesignerAppUtilsTest.java b/designer-realize/src/test/java/com/fr/design/mainframe/app/DesignerAppUtilsTest.java new file mode 100644 index 0000000000..c2f7ef7695 --- /dev/null +++ b/designer-realize/src/test/java/com/fr/design/mainframe/app/DesignerAppUtilsTest.java @@ -0,0 +1,106 @@ +package com.fr.design.mainframe.app; + +import com.fr.invoke.Reflect; +import com.fr.plugin.context.PluginMarker; +import com.fr.plugin.context.PluginMarkerAdapter; +import com.fr.plugin.engine.remote.PluginRemoteSync; +import com.fr.stable.TemplateIOErrorContextHolder; +import com.fr.third.guava.collect.Multimap; +import org.easymock.EasyMock; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.api.easymock.PowerMock; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; + +/** + * @author vito + * @version 10.0 + * Created by vito on 2021/5/31 + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({PluginRemoteSync.class}) +public class DesignerAppUtilsTest { + @Test + public void testDealWithErrorDetailMultiLineAndCache() { + TemplateIOErrorContextHolder.registerPluginNameMap(new HashMap() {{ + put("2", "好用的插件"); + }},new HashSet<>()); + TemplateIOErrorContextHolder.addNeedEnablePlugin(PluginMarkerAdapter.create("1", "1.0", "1插件")); + TemplateIOErrorContextHolder.addNeedInstallPlugin(PluginMarker.create("2", "1.0")); + TemplateIOErrorContextHolder.addNeedInstallPlugin(PluginMarker.create("3", "1.0")); + String log = DesignerAppUtils.dealWithErrorDetailMultiLineAndCache("template1"); + Assert.assertTrue(log.contains("1插件")); + Assert.assertTrue(log.contains("好用的插件")); + Assert.assertTrue(log.contains("3")); + Multimap map = DesignerAppUtils.popPluginInfoMap("template1"); + Assert.assertEquals(3, map.size()); + Assert.assertNull(DesignerAppUtils.popPluginInfoMap("template1")); + } + + @Test + public void testInvalidatePlugins() { + TemplateIOErrorContextHolder.registerPluginNameMap(new HashMap() {{ + put("2", "好用的插件"); + }},new HashSet<>()); + TemplateIOErrorContextHolder.addNeedEnablePlugin(PluginMarkerAdapter.create("1", "1.0", "1插件")); + TemplateIOErrorContextHolder.addNeedInstallPlugin(PluginMarker.create("2", "1.0")); + TemplateIOErrorContextHolder.addNeedInstallPlugin(PluginMarker.create("3", "1.0")); + String log = DesignerAppUtils.dealWithErrorDetailMultiLineAndCache("template1"); + Assert.assertTrue(log.contains("1插件")); + Assert.assertTrue(log.contains("好用的插件")); + Assert.assertTrue(log.contains("3")); + DesignerAppUtils.invalidatePlugins("template1"); + Assert.assertNull(DesignerAppUtils.popPluginInfoMap("template1")); + } + + @Test + public void testRearrange(){ + // 远程插件模拟注册 + PluginRemoteSync pluginRemoteSync = EasyMock.createMock(PluginRemoteSync.class); + EasyMock.expect(pluginRemoteSync.getPluginRemoteStatusByIdIndex()).andReturn(new HashMap(){{ + put("com.fr.plugin1", Reflect.on(PluginRemoteSync.PluginStatus.class).call("create","com.fr.plugin1","1",true).get()); + put("com.fr.plugin2", Reflect.on(PluginRemoteSync.PluginStatus.class).call("create","com.fr.plugin2","1",true).get()); + put("com.fr.plugin3", Reflect.on(PluginRemoteSync.PluginStatus.class).call("create","com.fr.plugin3","1",false).get()); + put("com.fr.plugin4", Reflect.on(PluginRemoteSync.PluginStatus.class).call("create","com.fr.plugin4","1",false).get()); + }}).anyTimes(); + EasyMock.replay(pluginRemoteSync); + PowerMock.mockStaticPartial(PluginRemoteSync.class, "getInstance"); + EasyMock.expect(PluginRemoteSync.getInstance()).andReturn(pluginRemoteSync).anyTimes(); + PowerMock.replay(PluginRemoteSync.class); + + // 本地插件模拟检查 + TemplateIOErrorContextHolder.registerPluginNameMap(new HashMap() {{ + put("com.fr.plugin1", "好用的插件1"); + put("com.fr.plugin2", "好用的插件2"); + put("com.fr.plugin3", "好用的插件3"); + put("com.fr.plugin4", "好用的插件4"); + put("com.fr.plugin5", "好用的插件5"); + }},new HashSet<>()); + // unknown + TemplateIOErrorContextHolder.addNeedInstallPlugin(PluginMarker.create("com.fr.plugin7", "1")); + // disable + TemplateIOErrorContextHolder.addNeedEnablePlugin(PluginMarkerAdapter.create("com.fr.plugin5", "1", "plugin5")); + TemplateIOErrorContextHolder.addNeedInstallPlugin(PluginMarker.create("com.fr.plugin3", "1")); + // not install + TemplateIOErrorContextHolder.addNeedInstallPlugin(PluginMarker.create("com.fr.plugin1", "1")); + TemplateIOErrorContextHolder.addNeedInstallPlugin(PluginMarker.create("com.fr.plugin4", "1")); + + Multimap pendingPlugins = TemplateIOErrorContextHolder.getPendingPlugin(); + + Reflect.on(DesignerAppUtils.class).call("rearrange",pendingPlugins).get(); + Assert.assertEquals(1,pendingPlugins.get(TemplateIOErrorContextHolder.UNKNOWN_PLUGIN).size()); + Collection pluginMarkerAdapters = pendingPlugins.get(TemplateIOErrorContextHolder.DISABLE_PLUGIN); + Assert.assertEquals(2, pluginMarkerAdapters.size()); + pluginMarkerAdapters.contains(PluginMarker.create("com.fr.plugin3", "1")); + pluginMarkerAdapters.contains(PluginMarker.create("com.fr.plugin4", "1")); + Collection pluginMarkerAdapters1 = pendingPlugins.get(TemplateIOErrorContextHolder.NOT_INSTALLED_PLUGIN); + Assert.assertEquals(1, pluginMarkerAdapters1.size()); + pluginMarkerAdapters1.contains(PluginMarker.create("com.fr.plugin5","1")); + } +}