]> source.dussan.org Git - pf4j.git/commitdiff
Support multiple plugin root directories (#404)
authorrreich <rainer.reich@coremedia.com>
Thu, 5 Nov 2020 13:37:08 +0000 (14:37 +0100)
committerGitHub <noreply@github.com>
Thu, 5 Nov 2020 13:37:08 +0000 (15:37 +0200)
12 files changed:
pf4j/src/main/java/org/pf4j/AbstractPluginManager.java
pf4j/src/main/java/org/pf4j/BasePluginRepository.java
pf4j/src/main/java/org/pf4j/DefaultPluginManager.java
pf4j/src/main/java/org/pf4j/DefaultPluginRepository.java
pf4j/src/main/java/org/pf4j/DevelopmentPluginRepository.java
pf4j/src/main/java/org/pf4j/JarPluginManager.java
pf4j/src/main/java/org/pf4j/JarPluginRepository.java
pf4j/src/main/java/org/pf4j/PluginManager.java
pf4j/src/main/java/org/pf4j/ZipPluginManager.java
pf4j/src/test/java/org/pf4j/DefaultPluginRepositoryTest.java
pf4j/src/test/java/org/pf4j/LoadPluginsFromMultipleRootsTest.java [new file with mode: 0644]
pf4j/src/test/java/org/pf4j/LoadPluginsTest.java

index 3d109e28929d22daed2a049bf68e94aa9351af27..95622573d2521ccd7b6ae86fdacec614f43d46d0 100644 (file)
@@ -25,12 +25,14 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * This class implements the boilerplate plugin code that any {@link PluginManager}
@@ -51,7 +53,7 @@ public abstract class AbstractPluginManager implements PluginManager {
     public static final String DEFAULT_PLUGINS_DIR = "plugins";
     public static final String DEVELOPMENT_PLUGINS_DIR = "../plugins";
 
-    protected Path pluginsRoot;
+    protected final List<Path> pluginsRoots = new ArrayList<>();
 
     protected ExtensionFinder extensionFinder;
 
@@ -109,19 +111,28 @@ public abstract class AbstractPluginManager implements PluginManager {
     protected VersionManager versionManager;
 
     /**
-     * The plugins root is supplied by {@code System.getProperty("pf4j.pluginsDir", "plugins")}.
+     * The plugins roots are supplied as comma-separated list by {@code System.getProperty("pf4j.pluginsDir", "plugins")}.
      */
     public AbstractPluginManager() {
         initialize();
     }
 
     /**
-     * Constructs {@code AbstractPluginManager} with the given plugins root.
+     * Constructs {@code AbstractPluginManager} with the given plugins roots.
      *
-     * @param pluginsRoot the root to search for plugins
+     * @param pluginsRoots the roots to search for plugins
      */
-    public AbstractPluginManager(Path pluginsRoot) {
-        this.pluginsRoot = pluginsRoot;
+    public AbstractPluginManager(Path... pluginsRoots) {
+        this(Arrays.asList(pluginsRoots));
+    }
+
+    /**
+     * Constructs {@code AbstractPluginManager} with the given plugins roots.
+     *
+     * @param pluginsRoots the roots to search for plugins
+     */
+    public AbstractPluginManager(List<Path> pluginsRoots) {
+        this.pluginsRoots.addAll(pluginsRoots);
 
         initialize();
     }
@@ -200,12 +211,17 @@ public abstract class AbstractPluginManager implements PluginManager {
      */
     @Override
     public void loadPlugins() {
-        log.debug("Lookup plugins in '{}'", pluginsRoot);
-        // check for plugins root
-        if (Files.notExists(pluginsRoot) || !Files.isDirectory(pluginsRoot)) {
-            log.warn("No '{}' root", pluginsRoot);
+        log.debug("Lookup plugins in '{}'", pluginsRoots);
+        // check for plugins roots
+        if (pluginsRoots.isEmpty()) {
+            log.warn("No plugins roots configured");
             return;
         }
+        pluginsRoots.forEach(path -> {
+            if (Files.notExists(path) || !Files.isDirectory(path)) {
+                log.warn("No '{}' root", path);
+            }
+        });
 
         // get all plugin paths from repository
         List<Path> pluginPaths = pluginRepository.getPluginPaths();
@@ -599,8 +615,15 @@ public abstract class AbstractPluginManager implements PluginManager {
         return pluginLoader;
     }
 
+    @Override
     public Path getPluginsRoot() {
-        return pluginsRoot;
+        return pluginsRoots.stream()
+            .findFirst()
+            .orElseThrow(() -> new IllegalStateException("pluginsRoots have not been initialized, yet."));
+    }
+
+    public List<Path> getPluginsRoots() {
+        return Collections.unmodifiableList(pluginsRoots);
     }
 
     @Override
@@ -687,8 +710,8 @@ public abstract class AbstractPluginManager implements PluginManager {
 
         pluginStateListeners = new ArrayList<>();
 
-        if (pluginsRoot == null) {
-            pluginsRoot = createPluginsRoot();
+        if (pluginsRoots.isEmpty()) {
+            pluginsRoots.addAll(createPluginsRoot());
         }
 
         pluginRepository = createPluginRepository();
@@ -704,20 +727,24 @@ public abstract class AbstractPluginManager implements PluginManager {
     }
 
     /**
-     * Add the possibility to override the plugins root.
-     * If a {@link #PLUGINS_DIR_PROPERTY_NAME} system property is defined than this method returns that root.
+     * Add the possibility to override the plugins roots.
+     * If a {@link #PLUGINS_DIR_PROPERTY_NAME} system property is defined than this method returns that roots.
      * If {@link #getRuntimeMode()} returns {@link RuntimeMode#DEVELOPMENT} than {@link #DEVELOPMENT_PLUGINS_DIR}
      * is returned else this method returns {@link #DEFAULT_PLUGINS_DIR}.
      *
      * @return the plugins root
      */
-    protected Path createPluginsRoot() {
+    protected List<Path> createPluginsRoot() {
         String pluginsDir = System.getProperty(PLUGINS_DIR_PROPERTY_NAME);
-        if (pluginsDir == null) {
-            pluginsDir = isDevelopment() ? DEVELOPMENT_PLUGINS_DIR : DEFAULT_PLUGINS_DIR;
+        if (pluginsDir != null && !pluginsDir.isEmpty()) {
+            return Arrays.stream(pluginsDir.split(","))
+                .map(String::trim)
+                .map(Paths::get)
+                .collect(Collectors.toList());
         }
 
-        return Paths.get(pluginsDir);
+        pluginsDir = isDevelopment() ? DEVELOPMENT_PLUGINS_DIR : DEFAULT_PLUGINS_DIR;
+        return Collections.singletonList(Paths.get(pluginsDir));
     }
 
     /**
index ea5ad3be4f28e489ea6476957db4cc8f1594434d..9ab8f96e892553ab7c57483f2d01353d53d6b19c 100644 (file)
@@ -22,11 +22,11 @@ import java.io.FileFilter;
 import java.io.IOException;
 import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
-import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * @author Decebal Suiu
@@ -34,17 +34,21 @@ import java.util.List;
  */
 public class BasePluginRepository implements PluginRepository {
 
-    protected final Path pluginsRoot;
+    protected final List<Path> pluginsRoots;
 
     protected FileFilter filter;
     protected Comparator<File> comparator;
 
-    public BasePluginRepository(Path pluginsRoot) {
-        this(pluginsRoot, null);
+    public BasePluginRepository(Path... pluginsRoots) {
+        this(Arrays.asList(pluginsRoots));
     }
 
-    public BasePluginRepository(Path pluginsRoot, FileFilter filter) {
-        this.pluginsRoot = pluginsRoot;
+    public BasePluginRepository(List<Path> pluginsRoots) {
+        this(pluginsRoots, null);
+    }
+
+    public BasePluginRepository(List<Path> pluginsRoots, FileFilter filter) {
+        this.pluginsRoots = pluginsRoots;
         this.filter = filter;
 
         // last modified file is first
@@ -67,22 +71,11 @@ public class BasePluginRepository implements PluginRepository {
 
     @Override
     public List<Path> getPluginPaths() {
-        File[] files = pluginsRoot.toFile().listFiles(filter);
-
-        if ((files == null) || files.length == 0) {
-            return Collections.emptyList();
-        }
-
-        if (comparator != null) {
-            Arrays.sort(files, comparator);
-        }
-
-        List<Path> paths = new ArrayList<>(files.length);
-        for (File file : files) {
-            paths.add(file.toPath());
-        }
-
-        return paths;
+        return pluginsRoots.stream()
+            .flatMap(path -> streamFiles(path, filter))
+            .sorted(comparator)
+            .map(File::toPath)
+            .collect(Collectors.toList());
     }
 
     @Override
@@ -101,4 +94,11 @@ public class BasePluginRepository implements PluginRepository {
         }
     }
 
+    protected Stream<File> streamFiles(Path directory, FileFilter filter) {
+        File[] files = directory.toFile().listFiles(filter);
+        return files != null
+            ? Arrays.stream(files)
+            : Stream.empty();
+    }
+
 }
index 9cad187610decc00475d852ba0fec856cc0c1f8e..e2667d4e5a7a71b04356ae3b4509ba38ef43efd8 100644 (file)
@@ -21,6 +21,7 @@ import org.slf4j.LoggerFactory;
 
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.List;
 
 /**
  * Default implementation of the {@link PluginManager} interface.
@@ -41,8 +42,12 @@ public class DefaultPluginManager extends AbstractPluginManager {
         super();
     }
 
-    public DefaultPluginManager(Path pluginsRoot) {
-        super(pluginsRoot);
+    public DefaultPluginManager(Path... pluginsRoots) {
+        super(pluginsRoots);
+    }
+
+    public DefaultPluginManager(List<Path> pluginsRoots) {
+        super(pluginsRoots);
     }
 
     @Override
@@ -73,7 +78,11 @@ public class DefaultPluginManager extends AbstractPluginManager {
     @Override
     protected PluginStatusProvider createPluginStatusProvider() {
         String configDir = System.getProperty(PLUGINS_DIR_CONFIG_PROPERTY_NAME);
-        Path configPath = configDir != null ? Paths.get(configDir) : getPluginsRoot();
+        Path configPath = configDir != null
+            ? Paths.get(configDir)
+            : getPluginsRoots().stream()
+            .findFirst()
+            .orElseThrow(() -> new IllegalArgumentException("No pluginsRoot configured"));
 
         return new DefaultPluginStatusProvider(configPath);
     }
@@ -81,9 +90,9 @@ public class DefaultPluginManager extends AbstractPluginManager {
     @Override
     protected PluginRepository createPluginRepository() {
         return new CompoundPluginRepository()
-            .add(new DevelopmentPluginRepository(getPluginsRoot()), this::isDevelopment)
-            .add(new JarPluginRepository(getPluginsRoot()), this::isNotDevelopment)
-            .add(new DefaultPluginRepository(getPluginsRoot()), this::isNotDevelopment);
+            .add(new DevelopmentPluginRepository(getPluginsRoots()), this::isDevelopment)
+            .add(new JarPluginRepository(getPluginsRoots()), this::isNotDevelopment)
+            .add(new DefaultPluginRepository(getPluginsRoots()), this::isNotDevelopment);
     }
 
     @Override
index 2fafbc7a266a01cbd0aacf6b493cec07c4f0253f..82a447f67ee096e2475b8e0e9505d18a61923ebc 100644 (file)
@@ -29,6 +29,7 @@ import java.io.File;
 import java.io.FileFilter;
 import java.io.IOException;
 import java.nio.file.Path;
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -38,8 +39,12 @@ public class DefaultPluginRepository extends BasePluginRepository {
 
     private static final Logger log = LoggerFactory.getLogger(DefaultPluginRepository.class);
 
-    public DefaultPluginRepository(Path pluginsRoot) {
-        super(pluginsRoot);
+    public DefaultPluginRepository(Path... pluginsRoots) {
+        this(Arrays.asList(pluginsRoots));
+    }
+
+    public DefaultPluginRepository(List<Path> pluginsRoots) {
+        super(pluginsRoots);
 
         AndFileFilter pluginsFilter = new AndFileFilter(new DirectoryFileFilter());
         pluginsFilter.addFileFilter(new NotFileFilter(createHiddenPluginFilter()));
@@ -64,16 +69,18 @@ public class DefaultPluginRepository extends BasePluginRepository {
 
     private void extractZipFiles() {
         // expand plugins zip files
-        File[] zipFiles = pluginsRoot.toFile().listFiles(new ZipFileFilter());
-        if ((zipFiles != null) && zipFiles.length > 0) {
-            for (File pluginZip : zipFiles) {
-                try {
-                    FileUtils.expandIfZip(pluginZip.toPath());
-                } catch (IOException e) {
-                    log.error("Cannot expand plugin zip '{}'", pluginZip);
-                    log.error(e.getMessage(), e);
-                }
-            }
+        pluginsRoots.stream()
+            .flatMap(path -> streamFiles(path, new ZipFileFilter()))
+            .map(File::toPath)
+            .forEach(this::expandIfZip);
+    }
+
+    private void expandIfZip(Path filePath) {
+        try {
+            FileUtils.expandIfZip(filePath);
+        } catch (IOException e) {
+            log.error("Cannot expand plugin zip '{}'", filePath);
+            log.error(e.getMessage(), e);
         }
     }
 
index e8f2c69997b904ab6dc7740d757cca30bc33fc83..fdc74c9ac0da630ec49efbd01a5bb71c6d2fe2ea 100644 (file)
@@ -24,6 +24,8 @@ import org.pf4j.util.OrFileFilter;
 
 import java.io.FileFilter;
 import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
 
 /**
  * @author Decebal Suiu
@@ -33,8 +35,12 @@ public class DevelopmentPluginRepository extends BasePluginRepository {
     public static final String MAVEN_BUILD_DIR = "target";
     public static final String GRADLE_BUILD_DIR = "build";
 
-    public DevelopmentPluginRepository(Path pluginsRoot) {
-        super(pluginsRoot);
+    public DevelopmentPluginRepository(Path... pluginsRoots) {
+        this(Arrays.asList(pluginsRoots));
+    }
+
+    public DevelopmentPluginRepository(List<Path> pluginsRoots) {
+        super(pluginsRoots);
 
         AndFileFilter pluginsFilter = new AndFileFilter(new DirectoryFileFilter());
         pluginsFilter.addFileFilter(new NotFileFilter(createHiddenPluginFilter()));
index f280466e2cd0e7c85ec2446f6578e733c1e9d660..ebbc63dab92dea6b88a1bca6bfac9030126d0772 100644 (file)
@@ -30,8 +30,8 @@ public class JarPluginManager extends DefaultPluginManager {
         super();
     }
 
-    public JarPluginManager(Path pluginsRoot) {
-        super(pluginsRoot);
+    public JarPluginManager(Path... pluginsRoots) {
+        super(pluginsRoots);
     }
 
     @Override
@@ -49,8 +49,8 @@ public class JarPluginManager extends DefaultPluginManager {
     @Override
     protected PluginRepository createPluginRepository() {
         return new CompoundPluginRepository()
-            .add(new DevelopmentPluginRepository(getPluginsRoot()), this::isDevelopment)
-            .add(new JarPluginRepository(getPluginsRoot()), this::isNotDevelopment);
+            .add(new DevelopmentPluginRepository(getPluginsRoots()), this::isDevelopment)
+            .add(new JarPluginRepository(getPluginsRoots()), this::isNotDevelopment);
     }
 
 }
index a8be3ffb02887731982ea6c21aca7b3c1150b6ce..d629bbdf01e47b9bee93c6e6ac0ac11fbcc9a21a 100644 (file)
@@ -18,14 +18,20 @@ package org.pf4j;
 import org.pf4j.util.JarFileFilter;
 
 import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
 
 /**
  * @author Decebal Suiu
  */
 public class JarPluginRepository extends BasePluginRepository {
 
-    public JarPluginRepository(Path pluginsRoot) {
-        super(pluginsRoot, new JarFileFilter());
+    public JarPluginRepository(Path... pluginsRoots) {
+        this(Arrays.asList(pluginsRoots));
+    }
+
+    public JarPluginRepository(List<Path> pluginsRoots) {
+        super(pluginsRoots, new JarFileFilter());
     }
 
 }
index 248b1acc52b0b59448ddd7e753d7458022402251..b7176560b8133679eb0418fd9811f96e74324e1f 100644 (file)
@@ -205,12 +205,22 @@ public interface PluginManager {
     String getSystemVersion();
 
     /**
-     * Gets the path of the folder where plugins are installed.
+     * Gets the first path of the folders where plugins are installed.
+     *
+     * @deprecated Use {@link #getPluginsRoots()} instead to get all paths where plugins are could be installed.
      *
      * @return Path of plugins root
      */
+    @Deprecated
     Path getPluginsRoot();
 
+    /**
+     * Gets the a read-only list of all paths of the folders where plugins are installed.
+     *
+     * @return Paths of plugins roots
+     */
+    List<Path> getPluginsRoots();
+
     VersionManager getVersionManager();
 
 }
index a32d156e0d05c6c16b7ce2fa141b39b76172754a..a22f3c606eba02f83da98bda9b8614e576ff8033 100644 (file)
@@ -42,8 +42,8 @@ public class ZipPluginManager extends DefaultPluginManager {
     @Override
     protected PluginRepository createPluginRepository() {
         return new CompoundPluginRepository()
-            .add(new DevelopmentPluginRepository(getPluginsRoot()), this::isDevelopment)
-            .add(new DefaultPluginRepository(getPluginsRoot()), this::isNotDevelopment);
+            .add(new DevelopmentPluginRepository(getPluginsRoots()), this::isDevelopment)
+            .add(new DefaultPluginRepository(getPluginsRoots()), this::isNotDevelopment);
     }
 
 }
index 3e31811ba153d79c9ef8e445efd270a203851b20..b8ffd7c248aec1286cd415b2ac26805d5ebcd839 100644 (file)
  */
 package org.pf4j;
 
+import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.io.TempDir;
 import org.pf4j.plugin.PluginZip;
+import org.pf4j.util.FileUtils;
 
 import java.io.IOException;
 import java.nio.file.Files;
@@ -35,18 +36,40 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
  */
 public class DefaultPluginRepositoryTest {
 
-    @TempDir
-    Path pluginsPath;
+    Path pluginsPath1;
+    Path pluginsPath2;
 
     @BeforeEach
     public void setUp() throws IOException {
-        Path plugin1Path = Files.createDirectory(pluginsPath.resolve("plugin-1"));
+        pluginsPath1 = Files.createTempDirectory("junit-pf4j-");
+        pluginsPath2 = Files.createTempDirectory("junit-pf4j-");
+        Path plugin1Path = Files.createDirectory(pluginsPath1.resolve("plugin-1"));
         // Prove that we can delete a folder with a file inside
         Files.createFile(plugin1Path.resolve("myfile"));
         // Create a zip file for plugin-1 to test that it is deleted when plugin is deleted
-        new PluginZip.Builder(pluginsPath.resolve("plugin-1.zip"), "plugin-1").pluginVersion("1.0").build();
-        Files.createDirectory(pluginsPath.resolve("plugin-2"));
-        Files.createDirectory(pluginsPath.resolve("plugin-3"));
+        new PluginZip.Builder(pluginsPath1.resolve("plugin-1.zip"), "plugin-1").pluginVersion("1.0").build();
+        Files.createDirectory(pluginsPath2.resolve("plugin-2"));
+        Files.createDirectory(pluginsPath2.resolve("plugin-3"));
+    }
+
+    @AfterEach
+    public void tearDown() throws IOException {
+        FileUtils.delete(pluginsPath1);
+        FileUtils.delete(pluginsPath2);
+    }
+
+    /**
+     * Test of {@link DefaultPluginRepository#getPluginPaths()} method.
+     */
+    @Test
+    public void testGetPluginArchivesFromSinglePath() {
+        PluginRepository repository = new DefaultPluginRepository(pluginsPath2);
+
+        List<Path> pluginPaths = repository.getPluginPaths();
+
+        assertEquals(2, pluginPaths.size());
+        assertPathExists(pluginPaths, pluginsPath2.resolve("plugin-2"));
+        assertPathExists(pluginPaths, pluginsPath2.resolve("plugin-3"));
     }
 
     /**
@@ -54,14 +77,14 @@ public class DefaultPluginRepositoryTest {
      */
     @Test
     public void testGetPluginArchives() {
-        PluginRepository repository = new DefaultPluginRepository(pluginsPath);
+        PluginRepository repository = new DefaultPluginRepository(pluginsPath1, pluginsPath2);
 
         List<Path> pluginPaths = repository.getPluginPaths();
 
         assertEquals(3, pluginPaths.size());
-        assertPathExists(pluginPaths, pluginsPath.resolve("plugin-1"));
-        assertPathExists(pluginPaths, pluginsPath.resolve("plugin-2"));
-        assertPathExists(pluginPaths, pluginsPath.resolve("plugin-3"));
+        assertPathExists(pluginPaths, pluginsPath1.resolve("plugin-1"));
+        assertPathExists(pluginPaths, pluginsPath2.resolve("plugin-2"));
+        assertPathExists(pluginPaths, pluginsPath2.resolve("plugin-3"));
     }
 
     /**
@@ -69,18 +92,18 @@ public class DefaultPluginRepositoryTest {
      */
     @Test
     public void testDeletePluginPath() {
-        PluginRepository repository = new DefaultPluginRepository(pluginsPath);
+        PluginRepository repository = new DefaultPluginRepository(pluginsPath1, pluginsPath2);
 
-        assertTrue(Files.exists(pluginsPath.resolve("plugin-1.zip")));
-        assertTrue(repository.deletePluginPath(pluginsPath.resolve("plugin-1")));
-        assertFalse(Files.exists(pluginsPath.resolve("plugin-1.zip")));
-        assertTrue(repository.deletePluginPath(pluginsPath.resolve("plugin-3")));
-        assertFalse(repository.deletePluginPath(pluginsPath.resolve("plugin-4")));
+        assertTrue(Files.exists(pluginsPath1.resolve("plugin-1.zip")));
+        assertTrue(repository.deletePluginPath(pluginsPath1.resolve("plugin-1")));
+        assertFalse(Files.exists(pluginsPath1.resolve("plugin-1.zip")));
+        assertTrue(repository.deletePluginPath(pluginsPath2.resolve("plugin-3")));
+        assertFalse(repository.deletePluginPath(pluginsPath2.resolve("plugin-4")));
 
         List<Path> pluginPaths = repository.getPluginPaths();
 
         assertEquals(1, pluginPaths.size());
-        assertEquals(pluginsPath.relativize(pluginPaths.get(0)).toString(), "plugin-2");
+        assertEquals(pluginsPath2.relativize(pluginPaths.get(0)).toString(), "plugin-2");
     }
 
     private void assertPathExists(List<Path> paths, Path path) {
diff --git a/pf4j/src/test/java/org/pf4j/LoadPluginsFromMultipleRootsTest.java b/pf4j/src/test/java/org/pf4j/LoadPluginsFromMultipleRootsTest.java
new file mode 100644 (file)
index 0000000..2117a62
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2012-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.pf4j;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.pf4j.plugin.PluginZip;
+import org.pf4j.util.FileUtils;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class LoadPluginsFromMultipleRootsTest {
+
+    private DefaultPluginManager pluginManager;
+
+    Path pluginsPath1;
+    Path pluginsPath2;
+
+    @BeforeEach
+    public void setUp() throws IOException {
+        pluginsPath1 = Files.createTempDirectory("junit-pf4j-");
+        pluginsPath2 = Files.createTempDirectory("junit-pf4j-");
+        pluginManager = new DefaultPluginManager(pluginsPath1, pluginsPath2);
+    }
+
+    @AfterEach
+    public void tearDown() throws IOException {
+        FileUtils.delete(pluginsPath1);
+        FileUtils.delete(pluginsPath2);
+    }
+
+    @Test
+    public void load() throws Exception {
+        PluginZip pluginZip1 = new PluginZip.Builder(pluginsPath1.resolve("my-plugin-1.2.3.zip"), "myPlugin")
+            .pluginVersion("1.2.3")
+            .build();
+
+        PluginZip pluginZip2 = new PluginZip.Builder(pluginsPath2.resolve("my-other-plugin-4.5.6.zip"), "myOtherPlugin")
+            .pluginVersion("4.5.6")
+            .build();
+
+        assertTrue(Files.exists(pluginZip1.path()));
+        assertEquals(0, pluginManager.getPlugins().size());
+
+        pluginManager.loadPlugins();
+
+        assertTrue(Files.exists(pluginZip1.path()));
+        assertTrue(Files.exists(pluginZip1.unzippedPath()));
+        assertTrue(Files.exists(pluginZip2.path()));
+        assertTrue(Files.exists(pluginZip2.unzippedPath()));
+        assertEquals(2, pluginManager.getPlugins().size());
+        assertEquals(pluginZip1.pluginId(), pluginManager.idForPath(pluginZip1.unzippedPath()));
+        assertEquals(pluginZip2.pluginId(), pluginManager.idForPath(pluginZip2.unzippedPath()));
+    }
+
+}
index e3dac0222653da3d37ca6c0256bd9c455c9e287c..ef2376b4ad884366e50b5c29941a6ea0d0167d28 100644 (file)
@@ -23,6 +23,7 @@ import org.pf4j.plugin.PluginZip;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Collections;
 
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.equalTo;
@@ -200,6 +201,11 @@ public class LoadPluginsTest {
         assertEquals(pluginsPath, pluginManager.getPluginsRoot());
     }
 
+    @Test
+    public void getRoots() {
+        assertEquals(Collections.singletonList(pluginsPath), pluginManager.getPluginsRoots());
+    }
+
     @Test
     public void notAPlugin() {
         pluginsPath.resolve("not-a-zip");