diff --git a/pf4j/pom.xml b/pf4j/pom.xml index aeefe49..6343772 100644 --- a/pf4j/pom.xml +++ b/pf4j/pom.xml @@ -34,7 +34,7 @@ org.slf4j - slf4j-simple + slf4j-log4j12 1.7.5 test diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/AbstractPluginManager.java b/pf4j/src/main/java/ro/fortsoft/pf4j/AbstractPluginManager.java index fa0366b..07d1043 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/AbstractPluginManager.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/AbstractPluginManager.java @@ -85,11 +85,6 @@ public abstract class AbstractPluginManager implements PluginManager { */ private Version systemVersion = Version.forIntegers(0, 0, 0); - /** - * A relation between 'pluginPath' and 'pluginId' - */ - private Map pathToIdMap; - private PluginRepository pluginRepository; private PluginFactory pluginFactory; private ExtensionFactory extensionFactory; @@ -258,7 +253,6 @@ public abstract class AbstractPluginManager implements PluginManager { // remove the plugin plugins.remove(pluginId); getResolvedPlugins().remove(pluginWrapper); - pathToIdMap.remove(pluginWrapper.getPluginPath()); firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState)); @@ -654,7 +648,6 @@ public abstract class AbstractPluginManager implements PluginManager { startedPlugins = new ArrayList<>(); pluginStateListeners = new ArrayList<>(); - pathToIdMap = new HashMap<>(); if (pluginsRoot == null) { pluginsRoot = createPluginsRoot(); @@ -735,7 +728,8 @@ public abstract class AbstractPluginManager implements PluginManager { protected PluginWrapper loadPluginFromPath(Path pluginPath) throws PluginException { // test for plugin duplication - if (plugins.get(pathToIdMap.get(pluginPath)) != null) { + if (idForPath(pluginPath) != null) { + log.warn("Plugin {} already loaded", idForPath(pluginPath)); return null; } @@ -784,6 +778,20 @@ public abstract class AbstractPluginManager implements PluginManager { return pluginWrapper; } + /** + * Tests for already loaded plugins on given path + * @param pluginPath the path to investigate + * @return id of plugin or null if not loaded + */ + protected String idForPath(Path pluginPath) { + for (PluginWrapper plugin : plugins.values()) { + if (plugin.getPluginPath().equals(pluginPath)) { + return plugin.getPluginId(); + } + } + return null; + } + /** * Override this to change the validation criteria * @param descriptor the plugin descriptor to validate diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/util/FileUtils.java b/pf4j/src/main/java/ro/fortsoft/pf4j/util/FileUtils.java index ab34a19..03d86bf 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/util/FileUtils.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/util/FileUtils.java @@ -166,11 +166,12 @@ public class FileUtils { * @throws IOException on error */ public static Path expandIfZip(Path filePath) throws IOException { - String fileName = filePath.getFileName().toString(); - if (!ZIP_EXTENSIONS.contains(fileName.substring(fileName.lastIndexOf(".")))) { + if (!isZipFile(filePath)) { return filePath; } + FileTime pluginZipDate = Files.getLastModifiedTime(filePath); + String fileName = filePath.getFileName().toString(); Path pluginDirectory = filePath.resolveSibling(fileName.substring(0, fileName.lastIndexOf("."))); if (!Files.exists(pluginDirectory) || pluginZipDate.compareTo(Files.getLastModifiedTime(pluginDirectory)) > 0) { @@ -192,4 +193,13 @@ public class FileUtils { return pluginDirectory; } + + /** + * Return true only if path is a zip file + * @param path to a file/dir + * @return true if file with .zip ending + */ + public static boolean isZipFile(Path path) { + return Files.isRegularFile(path) && path.toString().toLowerCase().endsWith(".zip"); + } } diff --git a/pf4j/src/test/java/ro/fortsoft/pf4j/LoadPluginsTest.java b/pf4j/src/test/java/ro/fortsoft/pf4j/LoadPluginsTest.java new file mode 100644 index 0000000..6806595 --- /dev/null +++ b/pf4j/src/test/java/ro/fortsoft/pf4j/LoadPluginsTest.java @@ -0,0 +1,169 @@ +/* + * Copyright 2017 Decebal Suiu + * + * 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 ro.fortsoft.pf4j; + +import com.github.zafarkhaja.semver.Version; +import org.junit.Before; +import org.junit.Test; +import ro.fortsoft.pf4j.plugin.MockPluginManager; + +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; +import java.net.URI; +import java.nio.file.*; +import java.util.Collections; + +import static junit.framework.TestCase.assertNull; +import static org.junit.Assert.*; + +public class LoadPluginsTest { + private Path tmpDir; + private MockPluginManager pluginManager; + private MockZipPlugin p1; + private MockZipPlugin p2; + private MockZipPlugin p3; + + @Before + public void setup() throws IOException { + tmpDir = Files.createTempDirectory("pf4j-test"); + tmpDir.toFile().deleteOnExit(); + p1 = new MockZipPlugin("myPlugin", "1.2.3", "my-plugin-1.2.3", "my-plugin-1.2.3.zip"); + p2 = new MockZipPlugin("myPlugin", "2.0.0", "my-plugin-2.0.0", "my-plugin-2.0.0.ZIP"); + p3 = new MockZipPlugin("other", "3.0.0", "other-3.0.0", "other-3.0.0.Zip"); + pluginManager = new MockPluginManager( + tmpDir, + new PropertiesPluginDescriptorFinder("my.properties")); + } + + @Test + public void load() throws Exception { + p1.create(); + assertTrue(Files.exists(p1.zipFile)); + assertEquals(0, pluginManager.getPlugins().size()); + pluginManager.loadPlugins(); + assertTrue(Files.exists(p1.zipFile)); + assertTrue(Files.exists(p1.unzipped)); + assertEquals(1, pluginManager.getPlugins().size()); + assertEquals(p1.id, pluginManager.idForPath(p1.unzipped)); + } + + @Test(expected = IllegalArgumentException.class) + public void loadNonExisting() throws Exception { + pluginManager.loadPlugin(Paths.get("nonexisting")); + } + + @Test + public void loadTwiceFails() throws Exception { + p1.create(); + assertNotNull(pluginManager.loadPluginFromPath(p1.zipFile)); + assertNull(pluginManager.loadPluginFromPath(p1.zipFile)); + } + + @Test + public void loadUnloadLoad() throws Exception { + p1.create(); + pluginManager.loadPlugins(); + assertEquals(1, pluginManager.getPlugins().size()); + assertTrue(pluginManager.unloadPlugin(pluginManager.idForPath(p1.unzipped))); + // duplicate check + assertNull(pluginManager.idForPath(p1.unzipped)); + // Double unload ok + assertFalse(pluginManager.unloadPlugin(pluginManager.idForPath(p1.unzipped))); + assertNotNull(pluginManager.loadPlugin(p1.unzipped)); + } + + @Test + public void upgrade() throws Exception { + p1.create(); + pluginManager.loadPlugins(); + pluginManager.startPlugins(); + assertEquals(1, pluginManager.getPlugins().size()); + assertEquals(Version.valueOf("1.2.3"), pluginManager.getPlugin(p2.id).getDescriptor().getVersion()); + assertEquals(1, pluginManager.getStartedPlugins().size()); + p2.create(); + pluginManager.loadPlugins(); + pluginManager.startPlugin(p2.id); + assertEquals(1, pluginManager.getPlugins().size()); + assertEquals(Version.valueOf("2.0.0"), pluginManager.getPlugin(p2.id).getDescriptor().getVersion()); + assertEquals(Version.valueOf("2.0.0"), pluginManager.getStartedPlugins().get(1).getDescriptor().getVersion()); + } + + @Test + public void getRoot() throws Exception { + assertEquals(tmpDir, pluginManager.getPluginsRoot()); + } + + @Test + public void notAPlugin() throws Exception { + Path notAPlugin = tmpDir.resolve("not-a-zip"); + Files.createFile(notAPlugin); + pluginManager.loadPlugins(); + assertEquals(0, pluginManager.getPlugins().size()); + } + + @Test + public void deletePlugin() throws Exception { + p1.create(); + p3.create(); + pluginManager.loadPlugins(); + pluginManager.startPlugins(); + assertEquals(2, pluginManager.getPlugins().size()); + pluginManager.deletePlugin(p1.id); + assertEquals(1, pluginManager.getPlugins().size()); + assertFalse(Files.exists(p1.zipFile)); + assertFalse(Files.exists(p1.unzipped)); + assertTrue(Files.exists(p3.zipFile)); + assertTrue(Files.exists(p3.unzipped)); + } + + private class MockZipPlugin { + public final String id; + public final String version; + public final String filename; + public final Path zipFile; + public final Path unzipped; + public final Path propsFile; + public final URI fileURI; + public String zipname; + + public MockZipPlugin(String id, String version, String filename, String zipname) throws IOException { + this.id = id; + this.version = version; + this.filename = filename; + this.zipname = zipname; + + zipFile = tmpDir.resolve(zipname).toAbsolutePath(); + unzipped = tmpDir.resolve(filename); + propsFile = tmpDir.resolve("my.properties"); + fileURI = URI.create("jar:file:"+zipFile.toString()); + } + + public void create() throws IOException { + try (FileSystem zipfs = FileSystems.newFileSystem(fileURI, Collections.singletonMap("create", "true"))) { + Path propsInZip = zipfs.getPath("/" + propsFile.getFileName().toString()); + BufferedWriter br = new BufferedWriter(new FileWriter(propsFile.toString())); + br.write("plugin.id=" + id); + br.newLine(); + br.write("plugin.version=" + version); + br.newLine(); + br.write("plugin.class=ro.fortsoft.pf4j.plugin.TestPlugin"); + br.close(); + Files.move(propsFile, propsInZip); + } + } + } +} diff --git a/pf4j/src/test/java/ro/fortsoft/pf4j/plugin/MockPluginManager.java b/pf4j/src/test/java/ro/fortsoft/pf4j/plugin/MockPluginManager.java new file mode 100644 index 0000000..23b1b34 --- /dev/null +++ b/pf4j/src/test/java/ro/fortsoft/pf4j/plugin/MockPluginManager.java @@ -0,0 +1,49 @@ +/* + * Copyright 2017 Decebal Suiu + * + * 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 ro.fortsoft.pf4j.plugin; + +import ro.fortsoft.pf4j.DefaultPluginClasspath; +import ro.fortsoft.pf4j.DefaultPluginDescriptorFinder; +import ro.fortsoft.pf4j.DefaultPluginManager; +import ro.fortsoft.pf4j.PluginDescriptorFinder; + +import java.nio.file.Path; + +/** + * Manager for testing + */ +public class MockPluginManager extends DefaultPluginManager { + private PluginDescriptorFinder finder = new DefaultPluginDescriptorFinder(new DefaultPluginClasspath()); + + public MockPluginManager() { + super(); + } + + public MockPluginManager(Path root, PluginDescriptorFinder finder) { + super(root); + this.finder = finder; + } + + @Override + protected PluginDescriptorFinder getPluginDescriptorFinder() { + return finder; + } + + @Override + protected PluginDescriptorFinder createPluginDescriptorFinder() { + return finder; + } +} diff --git a/pf4j/src/test/java/ro/fortsoft/pf4j/util/FileUtilsTest.java b/pf4j/src/test/java/ro/fortsoft/pf4j/util/FileUtilsTest.java index fec02f8..37f0d10 100644 --- a/pf4j/src/test/java/ro/fortsoft/pf4j/util/FileUtilsTest.java +++ b/pf4j/src/test/java/ro/fortsoft/pf4j/util/FileUtilsTest.java @@ -60,6 +60,12 @@ public class FileUtilsTest { // Non-zip file remains unchanged assertEquals(propsFile, FileUtils.expandIfZip(propsFile)); + // File without .suffix + Path extra = Files.createFile(tmpDir.resolve("extra")); + assertEquals(extra, FileUtils.expandIfZip(extra)); + // Folder + Path folder = Files.createFile(tmpDir.resolve("folder")); + assertEquals(folder, FileUtils.expandIfZip(folder)); } }