From 617508ffb5af30d43f12f0695515f1f90716bd14 Mon Sep 17 00:00:00 2001 From: Decebal Suiu Date: Sat, 24 Jun 2017 14:19:03 +0300 Subject: [PATCH] Enforce dependencies versions (#150) --- .../fortsoft/pf4j/AbstractPluginManager.java | 49 ++- .../ro/fortsoft/pf4j/DependencyResolver.java | 293 ++++++++++++++---- .../ro/fortsoft/pf4j/PluginDependency.java | 1 - .../ro/fortsoft/pf4j/PluginDescriptor.java | 32 +- .../ro/fortsoft/pf4j/PluginException.java | 2 - .../pf4j/PluginNotFoundException.java | 37 --- .../ro/fortsoft/pf4j/util/DirectedGraph.java | 38 ++- .../java/ro/fortsoft/pf4j/util/FileUtils.java | 19 +- .../fortsoft/pf4j/DependencyResolverTest.java | 141 +++++++++ 9 files changed, 474 insertions(+), 138 deletions(-) delete mode 100644 pf4j/src/main/java/ro/fortsoft/pf4j/PluginNotFoundException.java create mode 100644 pf4j/src/test/java/ro/fortsoft/pf4j/DependencyResolverTest.java diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/AbstractPluginManager.java b/pf4j/src/main/java/ro/fortsoft/pf4j/AbstractPluginManager.java index 4bd25fc..71bcb2e 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/AbstractPluginManager.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/AbstractPluginManager.java @@ -50,7 +50,7 @@ public abstract class AbstractPluginManager implements PluginManager { protected Map plugins; /* - * A map of plugin class loaders (he key is the 'pluginId'). + * A map of plugin class loaders (the key is the 'pluginId'). */ private Map pluginClassLoaders; @@ -60,7 +60,7 @@ public abstract class AbstractPluginManager implements PluginManager { private List unresolvedPlugins; /** - * A list with resolved plugins (resolved dependency). + * A list with all resolved plugins (resolved dependency). */ private List resolvedPlugins; @@ -83,7 +83,7 @@ public abstract class AbstractPluginManager implements PluginManager { /* * The system version used for comparisons to the plugin requires attribute. */ - private Version systemVersion = Version.forIntegers(0, 0, 0); + private Version systemVersion = Version.forIntegers(0); private PluginRepository pluginRepository; private PluginFactory pluginFactory; @@ -394,7 +394,7 @@ public abstract class AbstractPluginManager implements PluginManager { } /** - * Stop the specified plugin and it's dependencies. + * Stop the specified plugin and it's dependents. */ @Override public PluginState stopPlugin(String pluginId) { @@ -612,7 +612,7 @@ public abstract class AbstractPluginManager implements PluginManager { } } - return (version != null) ? Version.valueOf(version) : Version.forIntegers(0, 0, 0); + return (version != null) ? Version.valueOf(version) : Version.forIntegers(0); } protected abstract PluginRepository createPluginRepository(); @@ -700,7 +700,7 @@ public abstract class AbstractPluginManager implements PluginManager { // If exact versions are not allowed in requires, rewrite to >= expression requires = ">=" + requires; } - if (systemVersion.equals(Version.forIntegers(0,0,0)) || systemVersion.satisfies(requires)) { + if (systemVersion.equals(Version.forIntegers(0)) || systemVersion.satisfies(requires)) { return true; } @@ -718,15 +718,36 @@ public abstract class AbstractPluginManager implements PluginManager { } protected void resolvePlugins() throws PluginException { - resolveDependencies(); - } + // extract plugins descriptors from "unresolvedPlugins" list + List descriptors = new ArrayList<>(); + for (PluginWrapper plugin : unresolvedPlugins) { + descriptors.add(plugin.getDescriptor()); + } - protected void resolveDependencies() throws PluginException { - dependencyResolver.resolve(unresolvedPlugins); - resolvedPlugins = dependencyResolver.getSortedPlugins(); - for (PluginWrapper pluginWrapper : resolvedPlugins) { + DependencyResolver.Result result = dependencyResolver.resolve(descriptors); + + if (result.hasCyclicDependency()) { + throw new DependencyResolver.CyclicDependencyException(); + } + + List notFoundDependencies = result.getNotFoundDependencies(); + if (!notFoundDependencies.isEmpty()) { + throw new DependencyResolver.DependenciesNotFoundException(notFoundDependencies); + } + + List wrongVersionDependencies = result.getWrongVersionDependencies(); + if (!wrongVersionDependencies.isEmpty()) { + throw new DependencyResolver.DependenciesWrongVersionException(wrongVersionDependencies); + } + + List sortedPlugins = result.getSortedPlugins(); + + // move plugins from "unresolved" to "resolved" + for (String pluginId : sortedPlugins) { + PluginWrapper pluginWrapper = plugins.get(pluginId); unresolvedPlugins.remove(pluginWrapper); - log.info("Plugin '{}' resolved", pluginWrapper.getDescriptor().getPluginId()); + resolvedPlugins.add(pluginWrapper); + log.info("Plugin '{}' resolved", pluginId); } } @@ -800,6 +821,7 @@ public abstract class AbstractPluginManager implements PluginManager { return plugin.getPluginId(); } } + return null; } @@ -841,4 +863,5 @@ public abstract class AbstractPluginManager implements PluginManager { public void setExactVersionAllowed(boolean exactVersionAllowed) { this.exactVersionAllowed = exactVersionAllowed; } + } diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/DependencyResolver.java b/pf4j/src/main/java/ro/fortsoft/pf4j/DependencyResolver.java index d45f677..65c2f33 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/DependencyResolver.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/DependencyResolver.java @@ -15,13 +15,16 @@ */ package ro.fortsoft.pf4j; +import com.github.zafarkhaja.semver.Version; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ro.fortsoft.pf4j.util.DirectedGraph; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * @author Decebal Suiu @@ -30,89 +33,263 @@ public class DependencyResolver { private static final Logger log = LoggerFactory.getLogger(DependencyResolver.class); - private List plugins; - private DirectedGraph dependenciesGraph; - private DirectedGraph dependentsGraph; + private DirectedGraph dependenciesGraph; // the value is 'pluginId' + private DirectedGraph dependentsGraph; // the value is 'pluginId' private boolean resolved; - public void resolve(List plugins) { - this.plugins = plugins; + public Result resolve(List plugins) { + // create graphs + dependenciesGraph = new DirectedGraph<>(); + dependentsGraph = new DirectedGraph<>(); + + // populate graphs + Map pluginByIds = new HashMap<>(); + for (PluginDescriptor plugin : plugins) { + addPlugin(plugin); + pluginByIds.put(plugin.getPluginId(), plugin); + } + + log.debug("Graph: {}", dependenciesGraph); - initGraph(); + // get a sorted list of dependencies + List sortedPlugins = dependenciesGraph.reverseTopologicalSort(); + log.debug("Plugins order: {}", sortedPlugins); + + // create the result object + Result result = new Result(sortedPlugins); resolved = true; - } - public List getDependecies(String pluginsId) { - if (!resolved) { - return Collections.emptyList(); + if (sortedPlugins != null) { // no cyclic dependency + // detect not found dependencies + for (String pluginId : sortedPlugins) { + if (!pluginByIds.containsKey(pluginId)) { + result.addNotFoundDependency(pluginId); + } + } } - return dependenciesGraph.getNeighbors(pluginsId); - } + // check dependencies versions + for (PluginDescriptor plugin : plugins) { + String pluginId = plugin.getPluginId(); + Version existingVersion = plugin.getVersion(); - public List getDependents(String pluginsId) { - if (!resolved) { - return Collections.emptyList(); + List dependents = getDependents(pluginId); + while (!dependents.isEmpty()) { + String dependentId = dependents.remove(0); + PluginDescriptor dependent = pluginByIds.get(dependentId); + String requiredVersion = getDependencyVersionSupport(dependent, pluginId); + boolean ok = checkDependencyVersion(requiredVersion, existingVersion); + if (!ok) { + result.addWrongDependencyVersion(new WrongDependencyVersion(pluginId, dependentId, existingVersion, requiredVersion)); + } + } } - return dependentsGraph.getNeighbors(pluginsId); + return result; } - /** - * Get the list of plugins in dependency sorted order. - */ - public List getSortedPlugins() throws PluginException { - if (!resolved) { - return Collections.emptyList(); + /** + * Retrieves the plugins ids that the given plugin id directly depends on. + * + * @param pluginId + * @return + */ + public List getDependencies(String pluginId) { + checkResolved(); + return dependenciesGraph.getNeighbors(pluginId); + } + + /** + * Retrieves the plugins ids that the given content is a direct dependency of. + * + * @param pluginId + * @return + */ + public List getDependents(String pluginId) { + checkResolved(); + return dependentsGraph.getNeighbors(pluginId); + } + + /** + * Check if an existing version of dependency is compatible with the required version (from plugin descriptor). + * + * @param requiredVersion + * @param existingVersion + * @return + */ + protected boolean checkDependencyVersion(String requiredVersion, Version existingVersion) { + return existingVersion.satisfies(requiredVersion); + } + + private void addPlugin(PluginDescriptor descriptor) { + String pluginId = descriptor.getPluginId(); + List dependencies = descriptor.getDependencies(); + if (dependencies.isEmpty()) { + dependenciesGraph.addVertex(pluginId); + dependentsGraph.addVertex(pluginId); + } else { + for (PluginDependency dependency : dependencies) { + dependenciesGraph.addEdge(pluginId, dependency.getPluginId()); + dependentsGraph.addEdge(dependency.getPluginId(), pluginId); + } } + } - log.debug("Graph: {}", dependenciesGraph); - List pluginsId = dependenciesGraph.reverseTopologicalSort(); + private void checkResolved() { + if (!resolved) { + throw new IllegalStateException("Call 'resolve' method first"); + } + } - if (pluginsId == null) { - throw new PluginException("Cyclic dependencies !!! {}", dependenciesGraph.toString()); - } + private String getDependencyVersionSupport(PluginDescriptor dependent, String dependencyId) { + List dependencies = dependent.getDependencies(); + for (PluginDependency dependency : dependencies) { + if (dependencyId.equals(dependency.getPluginId())) { + return dependency.getPluginVersionSupport(); + } + } - log.debug("Plugins order: {}", pluginsId); - List sortedPlugins = new ArrayList<>(); - for (String pluginId : pluginsId) { - sortedPlugins.add(getPlugin(pluginId)); - } + throw new IllegalStateException("Cannot find a dependency with id '" + dependencyId + + "' for plugin '" + dependent.getPluginId() + "'"); + } - return sortedPlugins; - } + public static class Result { - private void initGraph() { - // create graph - dependenciesGraph = new DirectedGraph<>(); - dependentsGraph = new DirectedGraph<>(); + private boolean cyclicDependency; + private List notFoundDependencies; // value is "pluginId" + private List sortedPlugins; // value is "pluginId" + private List wrongVersionDependencies; - // populate graph - for (PluginWrapper pluginWrapper : plugins) { - PluginDescriptor descriptor = pluginWrapper.getDescriptor(); - String pluginId = descriptor.getPluginId(); - List dependencies = descriptor.getDependencies(); - if (!dependencies.isEmpty()) { - for (PluginDependency dependency : dependencies) { - dependenciesGraph.addEdge(pluginId, dependency.getPluginId()); - dependentsGraph.addEdge(dependency.getPluginId(), pluginId); - } + Result(List sortedPlugins) { + if (sortedPlugins == null) { + cyclicDependency = true; + this.sortedPlugins = Collections.emptyList(); } else { - dependenciesGraph.addVertex(pluginId); - dependentsGraph.addVertex(pluginId); + this.sortedPlugins = new ArrayList<>(sortedPlugins); } + + notFoundDependencies = new ArrayList<>(); + wrongVersionDependencies = new ArrayList<>(); + } + + /** + * Returns true is a cyclic dependency was detected. + */ + public boolean hasCyclicDependency() { + return cyclicDependency; + } + + /** + * Returns a list with dependencies required that were not found. + */ + public List getNotFoundDependencies() { + return notFoundDependencies; + } + + /** + * Returns a list with dependencies with wrong version. + */ + public List getWrongVersionDependencies() { + return wrongVersionDependencies; + } + + /** + * Get the list of plugins in dependency sorted order. + */ + public List getSortedPlugins() { + return sortedPlugins; } + + void addNotFoundDependency(String pluginId) { + notFoundDependencies.add(pluginId); + } + + void addWrongDependencyVersion(WrongDependencyVersion wrongDependencyVersion) { + wrongVersionDependencies.add(wrongDependencyVersion); + } + + } + + public static class WrongDependencyVersion { + + private String dependencyId; // value is "pluginId" + private String dependentId; // value is "pluginId" + private Version existingVersion; + private String requiredVersion; + + WrongDependencyVersion(String dependencyId, String dependentId, Version existingVersion, String requiredVersion) { + this.dependencyId = dependencyId; + this.dependentId = dependentId; + this.existingVersion = existingVersion; + this.requiredVersion = requiredVersion; + } + + public String getDependencyId() { + return dependencyId; + } + + public String getDependentId() { + return dependentId; + } + + public Version getExistingVersion() { + return existingVersion; + } + + public String getRequiredVersion() { + return requiredVersion; + } + + } + + /** + * It will be thrown if a cyclic dependency is detected. + */ + public static class CyclicDependencyException extends PluginException { + + public CyclicDependencyException() { + super("Cyclic dependencies"); + } + + } + + /** + * Indicates that the dependencies required were not found. + */ + public static class DependenciesNotFoundException extends PluginException { + + private List dependencies; + + public DependenciesNotFoundException(List dependencies) { + super("Dependencies '{}' not found", dependencies); + + this.dependencies = dependencies; + } + + public List getDependencies() { + return dependencies; + } + } - private PluginWrapper getPlugin(String pluginId) throws PluginNotFoundException { - for (PluginWrapper pluginWrapper : plugins) { - if (pluginId.equals(pluginWrapper.getDescriptor().getPluginId())) { - return pluginWrapper; - } - } + /** + * Indicates that some dependencies have wrong version. + */ + public static class DependenciesWrongVersionException extends PluginException { + + private List dependencies; + + public DependenciesWrongVersionException(List dependencies) { + super("Dependencies '{}' have wrong version", dependencies); - throw new PluginNotFoundException(pluginId); - } + this.dependencies = dependencies; + } + + public List getDependencies() { + return dependencies; + } + + } } diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/PluginDependency.java b/pf4j/src/main/java/ro/fortsoft/pf4j/PluginDependency.java index 0e5930a..44a147f 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/PluginDependency.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/PluginDependency.java @@ -28,7 +28,6 @@ public class PluginDependency { if (index == -1) { this.pluginId = dependency; } else { - this.pluginId = dependency.substring(0, index); if (dependency.length() > index + 1) { this.pluginVersionSupport = dependency.substring(index + 1); diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/PluginDescriptor.java b/pf4j/src/main/java/ro/fortsoft/pf4j/PluginDescriptor.java index 65a1d43..c6b7db8 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/PluginDescriptor.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/PluginDescriptor.java @@ -109,31 +109,43 @@ public class PluginDescriptor { + license + "]"; } - void setPluginId(String pluginId) { + PluginDescriptor setPluginId(String pluginId) { this.pluginId = pluginId; + + return this; } - void setPluginDescription(String pluginDescription) { + PluginDescriptor setPluginDescription(String pluginDescription) { this.pluginDescription = pluginDescription; + + return this; } - void setPluginClass(String pluginClassName) { + PluginDescriptor setPluginClass(String pluginClassName) { this.pluginClass = pluginClassName; + + return this; } - void setPluginVersion(Version version) { + PluginDescriptor setPluginVersion(Version version) { this.version = version; + + return this; } - void setProvider(String provider) { + PluginDescriptor setProvider(String provider) { this.provider = provider; + + return this; } - void setRequires(String requires) { + PluginDescriptor setRequires(String requires) { this.requires = requires; + + return this; } - void setDependencies(String dependencies) { + PluginDescriptor setDependencies(String dependencies) { if (dependencies != null) { dependencies = dependencies.trim(); if (dependencies.isEmpty()) { @@ -154,10 +166,14 @@ public class PluginDescriptor { } else { this.dependencies = Collections.emptyList(); } + + return this; } - public void setLicense(String license) { + public PluginDescriptor setLicense(String license) { this.license = license; + + return this; } } diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/PluginException.java b/pf4j/src/main/java/ro/fortsoft/pf4j/PluginException.java index caa194a..3d4aec4 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/PluginException.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/PluginException.java @@ -24,8 +24,6 @@ import ro.fortsoft.pf4j.util.StringUtils; */ public class PluginException extends Exception { - private static final long serialVersionUID = 1L; - public PluginException() { super(); } diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/PluginNotFoundException.java b/pf4j/src/main/java/ro/fortsoft/pf4j/PluginNotFoundException.java deleted file mode 100644 index 7ea4b53..0000000 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/PluginNotFoundException.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2012 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; - -/** - * @author Decebal Suiu - */ -class PluginNotFoundException extends PluginException { - - private static final long serialVersionUID = 1L; - - private String pluginId; - - public PluginNotFoundException(String pluginId) { - super("Plugin '" + pluginId + "' not found."); - - this.pluginId = pluginId; - } - - public String getPluginId() { - return pluginId; - } - -} diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/util/DirectedGraph.java b/pf4j/src/main/java/ro/fortsoft/pf4j/util/DirectedGraph.java index 9a37f08..8970c78 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/util/DirectedGraph.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/util/DirectedGraph.java @@ -23,6 +23,8 @@ import java.util.Map; import java.util.Stack; /** + * See Wikipedia for more information. + * * @author Decebal Suiu */ public class DirectedGraph { @@ -38,7 +40,7 @@ public class DirectedGraph { * Add a vertex to the graph. Nothing happens if vertex is already in graph. */ public void addVertex(V vertex) { - if (neighbors.containsKey(vertex)) { + if (containsVertex(vertex)) { return; } @@ -52,38 +54,42 @@ public class DirectedGraph { return neighbors.containsKey(vertex); } + public void removeVertex(V vertex) { + neighbors.remove(vertex); + } + /** * Add an edge to the graph; if either vertex does not exist, it's added. * This implementation allows the creation of multi-edges and self-loops. */ public void addEdge(V from, V to) { - this.addVertex(from); - this.addVertex(to); + addVertex(from); + addVertex(to); neighbors.get(from).add(to); } /** * Remove an edge from the graph. Nothing happens if no such edge. - * @throws IllegalArgumentException if either vertex doesn't exist. + * @throws {@link IllegalArgumentException} if either vertex doesn't exist. */ - public void remove(V from, V to) { - if (!(this.containsVertex(from) && this.containsVertex(to))) { - throw new IllegalArgumentException("Nonexistent vertex"); + public void removeEdge(V from, V to) { + if (!containsVertex(from)) { + throw new IllegalArgumentException("Nonexistent vertex " + from); + } + + if (!containsVertex(to)) { + throw new IllegalArgumentException("Nonexistent vertex " + to); } neighbors.get(from).remove(to); } public List getNeighbors(V vertex) { - if (!neighbors.containsKey(vertex)) { - return new ArrayList(); - } - - return neighbors.get(vertex); + return containsVertex(vertex) ? neighbors.get(vertex) : new ArrayList(); } /** - * Report (as a Map) the out-degree of each vertex. + * Report (as a Map) the out-degree (the number of tail ends adjacent to a vertex) of each vertex. */ public Map outDegree() { Map result = new HashMap<>(); @@ -95,9 +101,9 @@ public class DirectedGraph { } /** - * Report (as a Map) the in-degree of each vertex. + * Report (as a Map) the in-degree (the number of head ends adjacent to a vertex) of each vertex. */ - public Map inDegree() { + public Map inDegree() { Map result = new HashMap<>(); for (V vertex : neighbors.keySet()) { result.put(vertex, 0); // all in-degrees are 0 @@ -113,6 +119,7 @@ public class DirectedGraph { /** * Report (as a List) the topological sort of the vertices; null for no such sort. + * See this for more information. */ public List topologicalSort() { Map degree = inDegree(); @@ -156,6 +163,7 @@ public class DirectedGraph { if (list == null) { return null; } + Collections.reverse(list); return list; 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 03d86bf..550243a 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/util/FileUtils.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/util/FileUtils.java @@ -18,7 +18,13 @@ package ro.fortsoft.pf4j.util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.*; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileFilter; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; @@ -26,7 +32,6 @@ 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; @@ -34,9 +39,8 @@ 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"); + private static final Logger log = LoggerFactory.getLogger(FileUtils.class); public static List readLines(File file, boolean ignoreComments) throws IOException { if (!file.exists() || !file.isFile()) { @@ -86,19 +90,23 @@ public class FileUtils { */ public static void delete(Path path) throws IOException { Files.walkFileTree(path, new SimpleFileVisitor() { + @Override public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException { if (!attrs.isSymbolicLink()) { Files.delete(path); } + return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { Files.delete(dir); + return FileVisitResult.CONTINUE; } + }); } @@ -140,6 +148,7 @@ public class FileUtils { return newPath; } } + return null; } @@ -151,6 +160,7 @@ public class FileUtils { if (path == null) { return; } + try { Files.delete(path); } catch (IOException ignored) { } @@ -202,4 +212,5 @@ public class FileUtils { 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/DependencyResolverTest.java b/pf4j/src/test/java/ro/fortsoft/pf4j/DependencyResolverTest.java new file mode 100644 index 0000000..e8d1749 --- /dev/null +++ b/pf4j/src/test/java/ro/fortsoft/pf4j/DependencyResolverTest.java @@ -0,0 +1,141 @@ +/* + * 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.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Decebal Suiu + */ +public class DependencyResolverTest { + + @Test + public void sortedPlugins() { + // create incomplete plugin descriptor (ignore some attributes) + PluginDescriptor pd1 = new PluginDescriptor() + .setPluginId("p1") + .setDependencies("p2"); + + PluginDescriptor pd2 = new PluginDescriptor() + .setPluginId("p2") + .setPluginVersion(Version.forIntegers(0)); // needed in "checkDependencyVersion" method + + List plugins = new ArrayList<>(); + plugins.add(pd1); + plugins.add(pd2); + + DependencyResolver resolver = new DependencyResolver(); + DependencyResolver.Result result = resolver.resolve(plugins); + + assertTrue(result.getNotFoundDependencies().isEmpty()); + assertEquals(result.getSortedPlugins(), Arrays.asList("p2", "p1")); + } + + @Test + public void notFoundDependencies() throws Exception { + PluginDescriptor pd1 = new PluginDescriptor() + .setPluginId("p1") + .setDependencies("p2, p3"); + + List plugins = new ArrayList<>(); + plugins.add(pd1); + + DependencyResolver resolver = new DependencyResolver(); + DependencyResolver.Result result = resolver.resolve(plugins); + + assertFalse(result.getNotFoundDependencies().isEmpty()); + assertEquals(result.getNotFoundDependencies(), Arrays.asList("p2", "p3")); + } + + @Test + public void cyclicDependencies() { + PluginDescriptor pd1 = new PluginDescriptor() + .setPluginId("p1") + .setPluginVersion(Version.forIntegers(0)) + .setDependencies("p2"); + + PluginDescriptor pd2 = new PluginDescriptor() + .setPluginId("p2") + .setPluginVersion(Version.forIntegers(0)) + .setDependencies("p3"); + + PluginDescriptor pd3 = new PluginDescriptor() + .setPluginId("p3") + .setPluginVersion(Version.forIntegers(0)) + .setDependencies("p1"); + + List plugins = new ArrayList<>(); + plugins.add(pd1); + plugins.add(pd2); + plugins.add(pd3); + + DependencyResolver resolver = new DependencyResolver(); + DependencyResolver.Result result = resolver.resolve(plugins); + + assertTrue(result.hasCyclicDependency()); + } + + @Test + public void wrongDependencyVersion() { + PluginDescriptor pd1 = new PluginDescriptor() + .setPluginId("p1") +// .setDependencies("p2@2.0.0"); // simple version + .setDependencies("p2@>=1.5.0 & <1.6.0"); // range version + + PluginDescriptor pd2 = new PluginDescriptor() + .setPluginId("p2") + .setPluginVersion(Version.forIntegers(1, 4)); + + List plugins = new ArrayList<>(); + plugins.add(pd1); + plugins.add(pd2); + + DependencyResolver resolver = new DependencyResolver(); + DependencyResolver.Result result = resolver.resolve(plugins); + + assertFalse(result.getWrongVersionDependencies().isEmpty()); + } + + @Test + public void goodDependencyVersion() { + PluginDescriptor pd1 = new PluginDescriptor() + .setPluginId("p1") + .setDependencies("p2@2.0.0"); + + PluginDescriptor pd2 = new PluginDescriptor() + .setPluginId("p2") + .setPluginVersion(Version.forIntegers(2)); + + List plugins = new ArrayList<>(); + plugins.add(pd1); + plugins.add(pd2); + + DependencyResolver resolver = new DependencyResolver(); + DependencyResolver.Result result = resolver.resolve(plugins); + + assertTrue(result.getWrongVersionDependencies().isEmpty()); + } + +}