diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/AbstractPluginManager.java b/pf4j/src/main/java/ro/fortsoft/pf4j/AbstractPluginManager.java index 6014717..fa0366b 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/AbstractPluginManager.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/AbstractPluginManager.java @@ -16,7 +16,6 @@ package ro.fortsoft.pf4j; import com.github.zafarkhaja.semver.Version; -import com.github.zafarkhaja.semver.expr.Expression; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ro.fortsoft.pf4j.util.StringUtils; diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java b/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java index 81bce1b..b9d6c67 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java @@ -17,6 +17,7 @@ package ro.fortsoft.pf4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ro.fortsoft.pf4j.util.FileUtils; import java.io.File; import java.nio.file.Path; @@ -106,4 +107,22 @@ public class DefaultPluginManager extends AbstractPluginManager { log.info("PF4J version {} in '{}' mode", getVersion(), getRuntimeMode()); } + /** + * Load a plugin from disk. If the path is a zip file, first unpack + * @param pluginPath plugin location on disk + * @return PluginWrapper for the loaded plugin or null if not loaded + * @throws PluginException if problems during load + */ + @Override + protected PluginWrapper loadPluginFromPath(Path pluginPath) throws PluginException { + // First unzip any ZIP files + try { + pluginPath = FileUtils.expandIfZip(pluginPath); + } catch (Exception e) { + log.warn("Failed to unzip " + pluginPath, e); + return null; + } + + return super.loadPluginFromPath(pluginPath); + } } diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginRepository.java b/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginRepository.java index f662bc4..197ae22 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginRepository.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginRepository.java @@ -24,7 +24,6 @@ import ro.fortsoft.pf4j.util.HiddenFilter; import ro.fortsoft.pf4j.util.NameFileFilter; import ro.fortsoft.pf4j.util.NotFileFilter; import ro.fortsoft.pf4j.util.OrFileFilter; -import ro.fortsoft.pf4j.util.Unzip; import ro.fortsoft.pf4j.util.ZipFileFilter; import java.io.File; @@ -55,7 +54,7 @@ public class DefaultPluginRepository extends BasePluginRepository { if ((pluginZips != null) && pluginZips.length > 0) { for (File pluginZip : pluginZips) { try { - expandPluginZip(pluginZip); + FileUtils.expandIfZip(pluginZip.toPath()); } catch (IOException e) { log.error("Cannot expand plugin zip '{}'", pluginZip); log.error(e.getMessage(), e); @@ -81,42 +80,4 @@ public class DefaultPluginRepository extends BasePluginRepository { return hiddenPluginFilter; } - - /** - * Unzip a plugin zip file in a directory that has the same name as the zip file - * and it's relative to {@code pluginsRoot}. - * For example if the zip file is {@code my-plugin.zip} then the resulted directory - * is {@code my-plugin}. - * - * @param pluginZip - * @return - * @throws IOException - */ - private File expandPluginZip(File pluginZip) throws IOException { - String fileName = pluginZip.getName(); - long pluginZipDate = pluginZip.lastModified(); - String pluginName = fileName.substring(0, fileName.length() - 4); - File pluginDirectory = pluginsRoot.resolve(pluginName).toFile(); - // check if exists root or the '.zip' file is "newer" than root - if (!pluginDirectory.exists() || (pluginZipDate > pluginDirectory.lastModified())) { - log.debug("Expand plugin zip '{}' in '{}'", pluginZip, pluginDirectory); - - // do not overwrite an old version, remove it - if (pluginDirectory.exists()) { - FileUtils.delete(pluginDirectory.toPath()); - } - - // create root for plugin - pluginDirectory.mkdirs(); - - // expand '.zip' file - Unzip unzip = new Unzip(); - unzip.setSource(pluginZip); - unzip.setDestination(pluginDirectory); - unzip.extract(); - } - - return pluginDirectory; - } - } 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 70b4e01..ab34a19 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/util/FileUtils.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/util/FileUtils.java @@ -15,13 +15,18 @@ */ package ro.fortsoft.pf4j.util; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.*; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileTime; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -29,8 +34,11 @@ import java.util.List; * @author Decebal Suiu */ public class FileUtils { + private static final Logger log = LoggerFactory.getLogger(FileUtils.class); + + private static final List ZIP_EXTENSIONS = Arrays.asList(".zip", ".ZIP", ".Zip"); - public static List readLines(File file, boolean ignoreComments) throws IOException { + public static List readLines(File file, boolean ignoreComments) throws IOException { if (!file.exists() || !file.isFile()) { return new ArrayList<>(); } @@ -147,4 +155,41 @@ public class FileUtils { Files.delete(path); } catch (IOException ignored) { } } + + /** + * Unzip a zip file in a directory that has the same name as the zip file. + * For example if the zip file is {@code my-plugin.zip} then the resulted directory + * is {@code my-plugin}. + * + * @param filePath the file to evaluate + * @return Path of unzipped folder or original path if this was not a zip file + * @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(".")))) { + return filePath; + } + FileTime pluginZipDate = Files.getLastModifiedTime(filePath); + Path pluginDirectory = filePath.resolveSibling(fileName.substring(0, fileName.lastIndexOf("."))); + + if (!Files.exists(pluginDirectory) || pluginZipDate.compareTo(Files.getLastModifiedTime(pluginDirectory)) > 0) { + // do not overwrite an old version, remove it + if (Files.exists(pluginDirectory)) { + FileUtils.delete(pluginDirectory); + } + + // create root for plugin + Files.createDirectories(pluginDirectory); + + // expand '.zip' file + Unzip unzip = new Unzip(); + unzip.setSource(filePath.toFile()); + unzip.setDestination(pluginDirectory.toFile()); + unzip.extract(); + log.info("Expanded plugin zip '{}' in '{}'", filePath.getFileName(), pluginDirectory.getFileName()); + } + + return pluginDirectory; + } } diff --git a/pf4j/src/test/java/ro/fortsoft/pf4j/util/FileUtilsTest.java b/pf4j/src/test/java/ro/fortsoft/pf4j/util/FileUtilsTest.java new file mode 100644 index 0000000..fec02f8 --- /dev/null +++ b/pf4j/src/test/java/ro/fortsoft/pf4j/util/FileUtilsTest.java @@ -0,0 +1,65 @@ +/* + * 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.util; + +import org.junit.Before; +import org.junit.Test; + +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 org.junit.Assert.*; + +public class FileUtilsTest { + private Path zipFile; + private Path tmpDir; + private Path propsFile; + + @Before + public void setup() throws IOException { + tmpDir = Files.createTempDirectory("pf4j-test"); + tmpDir.toFile().deleteOnExit(); + zipFile = tmpDir.resolve("my.zip").toAbsolutePath(); + propsFile = tmpDir.resolve("plugin.properties"); + URI file = URI.create("jar:file:"+zipFile.toString()); + try (FileSystem zipfs = FileSystems.newFileSystem(file, Collections.singletonMap("create", "true"))) { + Path propsInZip = zipfs.getPath("/plugin.properties"); + BufferedWriter br = new BufferedWriter(new FileWriter(propsFile.toString())); + br.write("plugin.id=test"); + br.newLine(); + br.write("plugin.version=1.2.3"); + br.newLine(); + br.write("plugin.class=foo.bar"); + br.close(); + Files.move(propsFile, propsInZip); + } + } + + @Test + public void expandIfZip() throws Exception { + Path unzipped = FileUtils.expandIfZip(zipFile); + assertEquals(tmpDir.resolve("my"), unzipped); + assertTrue(Files.exists(tmpDir.resolve("my/plugin.properties"))); + + // Non-zip file remains unchanged + assertEquals(propsFile, FileUtils.expandIfZip(propsFile)); + } + +}