]> source.dussan.org Git - pf4j.git/commitdiff
Fix #576 (#579)
authorDecebal Suiu <decebal.suiu@gmail.com>
Mon, 24 Jun 2024 11:40:14 +0000 (14:40 +0300)
committerGitHub <noreply@github.com>
Mon, 24 Jun 2024 11:40:14 +0000 (14:40 +0300)
pf4j/src/main/java/org/pf4j/AbstractPluginManager.java
pf4j/src/test/java/org/pf4j/AbstractPluginManagerTest.java
pf4j/src/test/java/org/pf4j/DefaultPluginManagerTest.java

index 286b624d3bd726b8b77a74918616b9e77ebc139f..b9ba5c541d5f327e7248fa945fe11c83fad8e1d2 100644 (file)
@@ -282,11 +282,23 @@ public abstract class AbstractPluginManager implements PluginManager {
      * @return true if the plugin was unloaded, otherwise false
      */
     protected boolean unloadPlugin(String pluginId, boolean unloadDependents) {
+        return unloadPlugin(pluginId, unloadDependents, true);
+    }
+
+    /**
+     * Unload the specified plugin and it's dependents.
+     *
+     * @param pluginId the pluginId of the plugin to unload
+     * @param unloadDependents if true, unload dependents
+     * @param resolveDependencies if true, resolve dependencies
+     * @return true if the plugin was unloaded, otherwise false
+     */
+    protected boolean unloadPlugin(String pluginId, boolean unloadDependents, boolean resolveDependencies) {
         if (unloadDependents) {
             List<String> dependents = dependencyResolver.getDependents(pluginId);
             while (!dependents.isEmpty()) {
                 String dependent = dependents.remove(0);
-                unloadPlugin(dependent, false);
+                unloadPlugin(dependent, false, false);
                 dependents.addAll(0, dependencyResolver.getDependents(dependent));
             }
         }
@@ -331,6 +343,11 @@ public abstract class AbstractPluginManager implements PluginManager {
             }
         }
 
+        // resolve the plugins again (update plugins graph)
+        if (resolveDependencies) {
+            resolveDependencies();
+        }
+
         return true;
     }
 
@@ -917,7 +934,7 @@ public abstract class AbstractPluginManager implements PluginManager {
         pluginId = pluginDescriptor.getPluginId();
 
         // add plugin to the list with plugins
-        plugins.put(pluginId, pluginWrapper);
+        addPlugin(pluginWrapper);
         getUnresolvedPlugins().add(pluginWrapper);
 
         // add plugin class loader to the list with class loaders
@@ -1114,6 +1131,10 @@ public abstract class AbstractPluginManager implements PluginManager {
         this.resolveRecoveryStrategy = resolveRecoveryStrategy;
     }
 
+    void addPlugin(PluginWrapper pluginWrapper) {
+        plugins.put(pluginWrapper.getPluginId(), pluginWrapper);
+    }
+
     /**
      * Strategy for handling the recovery of a plugin that could not be resolved
      * (loaded) due to a dependency problem.
index 07a083b0e0e433b393f0590a3a5bfd35d62767b8..88be865b0d0b15329370959610319c5cb1836b4f 100644 (file)
@@ -36,6 +36,8 @@ import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.withSettings;
@@ -49,7 +51,7 @@ public class AbstractPluginManagerTest {
 
     @BeforeEach
     void setUp() {
-        pluginManager = mock(AbstractPluginManager.class, withSettings()
+        pluginManager = mock(AbstractPluginManagerWithDefaultVersionManager.class, withSettings()
             .useConstructor()
             .defaultAnswer(CALLS_REAL_METHODS));
     }
@@ -119,11 +121,33 @@ public class AbstractPluginManagerTest {
         assertThrows(PluginNotFoundException.class, () -> pluginManager.checkPluginId("plugin1"));
     }
 
-    private PluginWrapper createPluginWrapper(String pluginId) {
-        DefaultPluginDescriptor pluginDescriptor = new DefaultPluginDescriptor();
-        pluginDescriptor.setPluginId(pluginId);
-        pluginDescriptor.setPluginVersion("1.2.3");
+    @Test
+    void unloadPluginCallsResolveDependenciesOnce() {
+        PluginWrapper pluginWrapper1 = createPluginWrapper("plugin1", "plugin2");
+        PluginWrapper pluginWrapper2 = createPluginWrapper("plugin2");
+
+        pluginManager.addPlugin(pluginWrapper1);
+        pluginManager.addPlugin(pluginWrapper2);
+        pluginManager.resolveDependencies();
+
+        // reset the mock to not count the explicit call of resolveDependencies
+        reset(pluginManager);
+
+        pluginManager.unloadPlugin("plugin2", true);
+
+        verify(pluginManager, times(1)).resolveDependencies();
+    }
+
+    private PluginWrapper createPluginWrapper(String pluginId, String... dependencies) {
+        PluginDescriptor pluginDescriptor = new DefaultPluginDescriptor()
+            .setPluginId(pluginId)
+            .setPluginVersion("1.2.3")
+            .setDependencies(String.join(", ", dependencies));
 
+        return createPluginWrapper(pluginDescriptor);
+    }
+
+    private PluginWrapper createPluginWrapper(PluginDescriptor pluginDescriptor) {
         PluginWrapper pluginWrapper = new PluginWrapper(pluginManager, pluginDescriptor, Paths.get("plugin1"), getClass().getClassLoader());
         Plugin plugin = mock(Plugin.class);
         pluginWrapper.setPluginFactory(wrapper -> plugin);
@@ -131,4 +155,17 @@ public class AbstractPluginManagerTest {
         return pluginWrapper;
     }
 
+    static abstract class AbstractPluginManagerWithDefaultVersionManager extends AbstractPluginManager {
+
+        public AbstractPluginManagerWithDefaultVersionManager() {
+            super();
+        }
+
+        @Override
+        protected VersionManager createVersionManager() {
+            return new DefaultVersionManager();
+        }
+
+    }
+
 }
index d10689f1f755b079c2d3eb9c2df8b08caf583f58..33f9db051d39171db340b1b6f2d0502fbdc86942 100644 (file)
@@ -396,4 +396,40 @@ public class DefaultPluginManagerTest {
         assertThrows(PluginNotFoundException.class, () -> pluginManager.stopPlugin(pluginZip2.pluginId()));
     }
 
+    @Test
+    void deletePluginWithDependency() throws IOException {
+        PluginZip pluginZip1 = new PluginZip.Builder(pluginsPath.resolve("my-first-plugin-1.1.1.zip"), "myPlugin1")
+            .pluginVersion("1.1.1")
+            .build();
+
+        PluginZip pluginZip2 = new PluginZip.Builder(pluginsPath.resolve("my-second-plugin-2.2.2.zip"), "myPlugin2")
+            .pluginVersion("2.2.2")
+            .pluginDependencies("myPlugin1")
+            .build();
+
+        PluginZip pluginZip3 = new PluginZip.Builder(pluginsPath.resolve("my-third-plugin-3.3.3.zip"), "myPlugin3")
+            .pluginVersion("3.3.3")
+            .pluginDependencies("myPlugin2")
+            .build();
+
+        pluginManager.loadPlugins();
+        pluginManager.startPlugins();
+
+        assertEquals(PluginState.STARTED, pluginManager.getPlugin(pluginZip1.pluginId()).getPluginState());
+        assertEquals(PluginState.STARTED, pluginManager.getPlugin(pluginZip2.pluginId()).getPluginState());
+        assertEquals(PluginState.STARTED, pluginManager.getPlugin(pluginZip3.pluginId()).getPluginState());
+
+        System.out.println("Stopping " + pluginZip2.pluginId());
+        pluginManager.stopPlugin(pluginZip2.pluginId());
+        assertEquals(PluginState.STOPPED, pluginManager.getPlugin(pluginZip2.pluginId()).getPluginState());
+
+        boolean deleted = pluginManager.deletePlugin(pluginZip2.pluginId());
+        assertTrue(deleted);
+
+        assertEquals(1, pluginManager.getPlugins().size()); // myPlugin1 should still be there, myPlugin2 and myPlugin3 should be gone
+
+        pluginManager.stopPlugin(pluginZip1.pluginId());
+        assertEquals(PluginState.STOPPED, pluginManager.getPlugin(pluginZip1.pluginId()).getPluginState());
+    }
+
 }