diff --git a/pf4j/src/main/java/org/pf4j/AbstractPluginManager.java b/pf4j/src/main/java/org/pf4j/AbstractPluginManager.java index f3a7231..78c261c 100644 --- a/pf4j/src/main/java/org/pf4j/AbstractPluginManager.java +++ b/pf4j/src/main/java/org/pf4j/AbstractPluginManager.java @@ -311,6 +311,7 @@ public abstract class AbstractPluginManager implements PluginManager { } // remove the plugin + pluginWrapper.setPluginState(PluginState.UNLOADED); plugins.remove(pluginId); getResolvedPlugins().remove(pluginWrapper); getUnresolvedPlugins().remove(pluginWrapper); diff --git a/pf4j/src/main/java/org/pf4j/PluginState.java b/pf4j/src/main/java/org/pf4j/PluginState.java index 989157f..6a6c1f9 100644 --- a/pf4j/src/main/java/org/pf4j/PluginState.java +++ b/pf4j/src/main/java/org/pf4j/PluginState.java @@ -20,7 +20,7 @@ package org.pf4j; *
* Lifecycle of a plugin: *
- * CREATED -> RESOLVED -> STARTED -> STOPPED + * CREATED -> RESOLVED -> STARTED -> STOPPED -> UNLOADED * CREATED -> DISABLED * CREATED -> FAILED * @@ -57,7 +57,14 @@ public enum PluginState { /** * Plugin failed to start. */ - FAILED("FAILED"); + FAILED("FAILED"), + + /** + * The plugin has been unloaded. After this event has been completed, the plugin's + * {@link ClassLoader} will be closed. + */ + UNLOADED("UNLOADED"), + ; private final String status; diff --git a/pf4j/src/test/java/org/pf4j/DefaultPluginManagerTest.java b/pf4j/src/test/java/org/pf4j/DefaultPluginManagerTest.java index 2e78ad2..d95a2d0 100644 --- a/pf4j/src/test/java/org/pf4j/DefaultPluginManagerTest.java +++ b/pf4j/src/test/java/org/pf4j/DefaultPluginManagerTest.java @@ -25,6 +25,9 @@ import org.pf4j.test.PluginZip; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -40,12 +43,17 @@ public class DefaultPluginManagerTest { private DefaultPluginDescriptor pluginDescriptor; private PluginWrapper pluginWrapper; + private ListreceivedEvents; + @TempDir Path pluginsPath; @BeforeEach public void setUp() throws IOException { + receivedEvents = new ArrayList<>(); + pluginManager = new DefaultPluginManager(pluginsPath); + pluginManager.addPluginStateListener(event -> receivedEvents.add(event)); pluginDescriptor = new DefaultPluginDescriptor(); pluginDescriptor.setPluginId("myPlugin"); @@ -63,6 +71,7 @@ public class DefaultPluginManagerTest { pluginManager = null; pluginDescriptor = null; pluginWrapper = null; + receivedEvents = null; } @Test @@ -140,6 +149,7 @@ public class DefaultPluginManagerTest { PluginManager pluginManager = new DefaultPluginManager(pluginsPath) { + @Override protected PluginStatusProvider createPluginStatusProvider() { return statusProvider; } @@ -171,6 +181,12 @@ public class DefaultPluginManagerTest { assertTrue(deleted); assertFalse(pluginZip.file().exists()); + + Optional unloadedEvent = receivedEvents.stream() + .filter(event -> event.getPluginState() == PluginState.UNLOADED) + .findFirst(); + + assertTrue(unloadedEvent.isPresent()); } @Test @@ -188,6 +204,12 @@ public class DefaultPluginManagerTest { assertTrue(deleted); assertFalse(pluginJar.file().exists()); + + Optional unloadedEvent = receivedEvents.stream() + .filter(event -> event.getPluginState() == PluginState.UNLOADED) + .findFirst(); + + assertTrue(unloadedEvent.isPresent()); } @Test @@ -310,4 +332,25 @@ public class DefaultPluginManagerTest { assertThrows(DependencyResolver.CyclicDependencyException.class, () -> pluginManager.loadPlugins()); } + @Test + public void deleteZipPluginForPluginThatHasNotBeenStartedPostsUnloadedEvent() throws Exception { + PluginZip pluginZip = new PluginZip.Builder(pluginsPath.resolve("my-plugin-1.2.3.zip"), "myPlugin") + .pluginVersion("1.2.3") + .build(); + + pluginManager.loadPlugin(pluginZip.path()); + + assertEquals(1, pluginManager.getPlugins().size()); + + boolean deleted = pluginManager.deletePlugin(pluginZip.pluginId()); + assertTrue(deleted); + + assertFalse(pluginZip.file().exists()); + + Optional unloadedEvent = receivedEvents.stream() + .filter(event -> event.getPluginState() == PluginState.UNLOADED) + .findFirst(); + + assertTrue(unloadedEvent.isPresent()); + } }