From eae83ae949f243daf3241d98a3c68370d173b98d Mon Sep 17 00:00:00 2001 From: John Niang Date: Wed, 24 Jul 2024 11:01:35 +0800 Subject: [PATCH] Fix the problem that plugins without jar file may not be deleted (#6334) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### What type of PR is this? /kind bug /area core /area plugin /milestone 2.18.x #### What this PR does / why we need it: This PR checks if the plugin is already unloaded while getting dependents to fix the problem that plugins without jar file may not be deleted or not be enabled or disabled. #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/6072 #### Special notes for your reviewer: 1. Try to move plugins folder to another folder 2. Restart Halo 3. Try to change state of plugins or delete plugins directly 4. See the result #### Does this PR introduce a user-facing change? ```release-note 修复在没有插件文件的情况下可能无法删除插件的问题 ``` --- .../halo/app/plugin/HaloPluginManager.java | 39 +++++++----- .../app/plugin/HaloPluginManagerTest.java | 63 +++++++++++++++++++ 2 files changed, 88 insertions(+), 14 deletions(-) create mode 100644 application/src/test/java/run/halo/app/plugin/HaloPluginManagerTest.java diff --git a/application/src/main/java/run/halo/app/plugin/HaloPluginManager.java b/application/src/main/java/run/halo/app/plugin/HaloPluginManager.java index 8d0fb1068e..4d131d4243 100644 --- a/application/src/main/java/run/halo/app/plugin/HaloPluginManager.java +++ b/application/src/main/java/run/halo/app/plugin/HaloPluginManager.java @@ -22,6 +22,7 @@ import org.pf4j.PluginStateListener; import org.pf4j.PluginStatusProvider; import org.pf4j.PluginWrapper; +import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; import org.springframework.data.util.Lazy; import run.halo.app.infra.SystemVersionSupplier; @@ -36,33 +37,27 @@ * @since 2.0.0 */ @Slf4j -public class HaloPluginManager extends DefaultPluginManager implements SpringPluginManager { +public class HaloPluginManager extends DefaultPluginManager + implements SpringPluginManager, InitializingBean { private final ApplicationContext rootContext; - private final Lazy sharedContext; + private Lazy sharedContext; private final PluginProperties pluginProperties; private final PluginsRootGetter pluginsRootGetter; + private final SystemVersionSupplier systemVersionSupplier; + public HaloPluginManager(ApplicationContext rootContext, PluginProperties pluginProperties, - SystemVersionSupplier systemVersionSupplier, PluginsRootGetter pluginsRootGetter) { + SystemVersionSupplier systemVersionSupplier, + PluginsRootGetter pluginsRootGetter) { this.pluginProperties = pluginProperties; this.rootContext = rootContext; - // We have to initialize share context lazily because the root context has not refreshed - this.sharedContext = Lazy.of(() -> SharedApplicationContextFactory.create(rootContext)); this.pluginsRootGetter = pluginsRootGetter; - super.runtimeMode = pluginProperties.getRuntimeMode(); - - setExactVersionAllowed(pluginProperties.isExactVersionAllowed()); - setSystemVersion(systemVersionSupplier.get().getNormalVersion()); - - super.initialize(); - - // the listener must be after the super#initialize - addPluginStateListener(new PluginStartedListener()); + this.systemVersionSupplier = systemVersionSupplier; } @Override @@ -71,6 +66,18 @@ protected void initialize() { // components before properties set. } + @Override + public void afterPropertiesSet() throws Exception { + super.runtimeMode = pluginProperties.getRuntimeMode(); + this.sharedContext = Lazy.of(() -> SharedApplicationContextFactory.create(rootContext)); + setExactVersionAllowed(pluginProperties.isExactVersionAllowed()); + setSystemVersion(systemVersionSupplier.get().toStableVersion().toString()); + + super.initialize(); + // the listener must be after the super#initialize + addPluginStateListener(new PluginStartedListener()); + } + @Override protected ExtensionFactory createExtensionFactory() { return new SpringExtensionFactory(this); @@ -154,6 +161,10 @@ public ApplicationContext getSharedContext() { @Override public List getDependents(String pluginId) { + if (getPlugin(pluginId) == null) { + return List.of(); + } + var dependents = new ArrayList(); var stack = new Stack(); dependencyResolver.getDependents(pluginId).forEach(stack::push); diff --git a/application/src/test/java/run/halo/app/plugin/HaloPluginManagerTest.java b/application/src/test/java/run/halo/app/plugin/HaloPluginManagerTest.java new file mode 100644 index 0000000000..d6711e1ab7 --- /dev/null +++ b/application/src/test/java/run/halo/app/plugin/HaloPluginManagerTest.java @@ -0,0 +1,63 @@ +package run.halo.app.plugin; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; + +import com.github.zafarkhaja.semver.Version; +import java.nio.file.Path; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.pf4j.RuntimeMode; +import org.springframework.context.ApplicationContext; +import run.halo.app.infra.SystemVersionSupplier; + +@ExtendWith(MockitoExtension.class) +class HaloPluginManagerTest { + + @Mock + PluginProperties pluginProperties; + + @Mock + SystemVersionSupplier systemVersionSupplier; + + @Mock + PluginsRootGetter pluginsRootGetter; + + @Mock + ApplicationContext rootContext; + + @InjectMocks + HaloPluginManager pluginManager; + + @TempDir + Path tempDir; + + @Test + void shouldGetDependentsWhilePluginsNotResolved() throws Exception { + when(pluginProperties.getRuntimeMode()).thenReturn(RuntimeMode.DEPLOYMENT); + when(systemVersionSupplier.get()).thenReturn(Version.of(1, 2, 3)); + when(pluginsRootGetter.get()).thenReturn(tempDir); + pluginManager.afterPropertiesSet(); + // if we don't invoke resolves + var dependents = pluginManager.getDependents("fake-plugin"); + assertTrue(dependents.isEmpty()); + } + + @Test + void shouldGetDependentsWhilePluginsResolved() throws Exception { + when(pluginProperties.getRuntimeMode()).thenReturn(RuntimeMode.DEPLOYMENT); + when(systemVersionSupplier.get()).thenReturn(Version.of(1, 2, 3)); + when(pluginsRootGetter.get()).thenReturn(tempDir); + pluginManager.afterPropertiesSet(); + pluginManager.loadPlugins(); + // if we don't invoke resolves + var dependents = pluginManager.getDependents("fake-plugin"); + assertTrue(dependents.isEmpty()); + } + + +}