Browse Source

Enforce dependencies versions (#150)

pull/154/head
Decebal Suiu 8 years ago committed by GitHub
parent
commit
617508ffb5
  1. 49
      pf4j/src/main/java/ro/fortsoft/pf4j/AbstractPluginManager.java
  2. 293
      pf4j/src/main/java/ro/fortsoft/pf4j/DependencyResolver.java
  3. 1
      pf4j/src/main/java/ro/fortsoft/pf4j/PluginDependency.java
  4. 32
      pf4j/src/main/java/ro/fortsoft/pf4j/PluginDescriptor.java
  5. 2
      pf4j/src/main/java/ro/fortsoft/pf4j/PluginException.java
  6. 37
      pf4j/src/main/java/ro/fortsoft/pf4j/PluginNotFoundException.java
  7. 38
      pf4j/src/main/java/ro/fortsoft/pf4j/util/DirectedGraph.java
  8. 19
      pf4j/src/main/java/ro/fortsoft/pf4j/util/FileUtils.java
  9. 141
      pf4j/src/test/java/ro/fortsoft/pf4j/DependencyResolverTest.java

49
pf4j/src/main/java/ro/fortsoft/pf4j/AbstractPluginManager.java

@ -50,7 +50,7 @@ public abstract class AbstractPluginManager implements PluginManager {
protected Map<String, PluginWrapper> plugins; protected Map<String, PluginWrapper> 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<String, ClassLoader> pluginClassLoaders; private Map<String, ClassLoader> pluginClassLoaders;
@ -60,7 +60,7 @@ public abstract class AbstractPluginManager implements PluginManager {
private List<PluginWrapper> unresolvedPlugins; private List<PluginWrapper> unresolvedPlugins;
/** /**
* A list with resolved plugins (resolved dependency). * A list with all resolved plugins (resolved dependency).
*/ */
private List<PluginWrapper> resolvedPlugins; private List<PluginWrapper> resolvedPlugins;
@ -83,7 +83,7 @@ public abstract class AbstractPluginManager implements PluginManager {
/* /*
* The system version used for comparisons to the plugin requires attribute. * 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 PluginRepository pluginRepository;
private PluginFactory pluginFactory; 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 @Override
public PluginState stopPlugin(String pluginId) { 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(); 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 // If exact versions are not allowed in requires, rewrite to >= expression
requires = ">=" + requires; requires = ">=" + requires;
} }
if (systemVersion.equals(Version.forIntegers(0,0,0)) || systemVersion.satisfies(requires)) { if (systemVersion.equals(Version.forIntegers(0)) || systemVersion.satisfies(requires)) {
return true; return true;
} }
@ -718,15 +718,36 @@ public abstract class AbstractPluginManager implements PluginManager {
} }
protected void resolvePlugins() throws PluginException { protected void resolvePlugins() throws PluginException {
resolveDependencies(); // extract plugins descriptors from "unresolvedPlugins" list
} List<PluginDescriptor> descriptors = new ArrayList<>();
for (PluginWrapper plugin : unresolvedPlugins) {
descriptors.add(plugin.getDescriptor());
}
protected void resolveDependencies() throws PluginException { DependencyResolver.Result result = dependencyResolver.resolve(descriptors);
dependencyResolver.resolve(unresolvedPlugins);
resolvedPlugins = dependencyResolver.getSortedPlugins(); if (result.hasCyclicDependency()) {
for (PluginWrapper pluginWrapper : resolvedPlugins) { throw new DependencyResolver.CyclicDependencyException();
}
List<String> notFoundDependencies = result.getNotFoundDependencies();
if (!notFoundDependencies.isEmpty()) {
throw new DependencyResolver.DependenciesNotFoundException(notFoundDependencies);
}
List<DependencyResolver.WrongDependencyVersion> wrongVersionDependencies = result.getWrongVersionDependencies();
if (!wrongVersionDependencies.isEmpty()) {
throw new DependencyResolver.DependenciesWrongVersionException(wrongVersionDependencies);
}
List<String> sortedPlugins = result.getSortedPlugins();
// move plugins from "unresolved" to "resolved"
for (String pluginId : sortedPlugins) {
PluginWrapper pluginWrapper = plugins.get(pluginId);
unresolvedPlugins.remove(pluginWrapper); 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 plugin.getPluginId();
} }
} }
return null; return null;
} }
@ -841,4 +863,5 @@ public abstract class AbstractPluginManager implements PluginManager {
public void setExactVersionAllowed(boolean exactVersionAllowed) { public void setExactVersionAllowed(boolean exactVersionAllowed) {
this.exactVersionAllowed = exactVersionAllowed; this.exactVersionAllowed = exactVersionAllowed;
} }
} }

293
pf4j/src/main/java/ro/fortsoft/pf4j/DependencyResolver.java

@ -15,13 +15,16 @@
*/ */
package ro.fortsoft.pf4j; package ro.fortsoft.pf4j;
import com.github.zafarkhaja.semver.Version;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import ro.fortsoft.pf4j.util.DirectedGraph; import ro.fortsoft.pf4j.util.DirectedGraph;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* @author Decebal Suiu * @author Decebal Suiu
@ -30,89 +33,263 @@ public class DependencyResolver {
private static final Logger log = LoggerFactory.getLogger(DependencyResolver.class); private static final Logger log = LoggerFactory.getLogger(DependencyResolver.class);
private List<PluginWrapper> plugins; private DirectedGraph<String> dependenciesGraph; // the value is 'pluginId'
private DirectedGraph<String> dependenciesGraph; private DirectedGraph<String> dependentsGraph; // the value is 'pluginId'
private DirectedGraph<String> dependentsGraph;
private boolean resolved; private boolean resolved;
public void resolve(List<PluginWrapper> plugins) { public Result resolve(List<PluginDescriptor> plugins) {
this.plugins = plugins; // create graphs
dependenciesGraph = new DirectedGraph<>();
dependentsGraph = new DirectedGraph<>();
// populate graphs
Map<String, PluginDescriptor> 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<String> sortedPlugins = dependenciesGraph.reverseTopologicalSort();
log.debug("Plugins order: {}", sortedPlugins);
// create the result object
Result result = new Result(sortedPlugins);
resolved = true; resolved = true;
}
public List<String> getDependecies(String pluginsId) { if (sortedPlugins != null) { // no cyclic dependency
if (!resolved) { // detect not found dependencies
return Collections.emptyList(); 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<String> getDependents(String pluginsId) { List<String> dependents = getDependents(pluginId);
if (!resolved) { while (!dependents.isEmpty()) {
return Collections.emptyList(); 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. * Retrieves the plugins ids that the given plugin id directly depends on.
*/ *
public List<PluginWrapper> getSortedPlugins() throws PluginException { * @param pluginId
if (!resolved) { * @return
return Collections.emptyList(); */
public List<String> 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<String> 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<PluginDependency> 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); private void checkResolved() {
List<String> pluginsId = dependenciesGraph.reverseTopologicalSort(); if (!resolved) {
throw new IllegalStateException("Call 'resolve' method first");
}
}
if (pluginsId == null) { private String getDependencyVersionSupport(PluginDescriptor dependent, String dependencyId) {
throw new PluginException("Cyclic dependencies !!! {}", dependenciesGraph.toString()); List<PluginDependency> dependencies = dependent.getDependencies();
} for (PluginDependency dependency : dependencies) {
if (dependencyId.equals(dependency.getPluginId())) {
return dependency.getPluginVersionSupport();
}
}
log.debug("Plugins order: {}", pluginsId); throw new IllegalStateException("Cannot find a dependency with id '" + dependencyId +
List<PluginWrapper> sortedPlugins = new ArrayList<>(); "' for plugin '" + dependent.getPluginId() + "'");
for (String pluginId : pluginsId) { }
sortedPlugins.add(getPlugin(pluginId));
}
return sortedPlugins; public static class Result {
}
private void initGraph() { private boolean cyclicDependency;
// create graph private List<String> notFoundDependencies; // value is "pluginId"
dependenciesGraph = new DirectedGraph<>(); private List<String> sortedPlugins; // value is "pluginId"
dependentsGraph = new DirectedGraph<>(); private List<WrongDependencyVersion> wrongVersionDependencies;
// populate graph Result(List<String> sortedPlugins) {
for (PluginWrapper pluginWrapper : plugins) { if (sortedPlugins == null) {
PluginDescriptor descriptor = pluginWrapper.getDescriptor(); cyclicDependency = true;
String pluginId = descriptor.getPluginId(); this.sortedPlugins = Collections.emptyList();
List<PluginDependency> dependencies = descriptor.getDependencies();
if (!dependencies.isEmpty()) {
for (PluginDependency dependency : dependencies) {
dependenciesGraph.addEdge(pluginId, dependency.getPluginId());
dependentsGraph.addEdge(dependency.getPluginId(), pluginId);
}
} else { } else {
dependenciesGraph.addVertex(pluginId); this.sortedPlugins = new ArrayList<>(sortedPlugins);
dependentsGraph.addVertex(pluginId);
} }
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<String> getNotFoundDependencies() {
return notFoundDependencies;
}
/**
* Returns a list with dependencies with wrong version.
*/
public List<WrongDependencyVersion> getWrongVersionDependencies() {
return wrongVersionDependencies;
}
/**
* Get the list of plugins in dependency sorted order.
*/
public List<String> 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<String> dependencies;
public DependenciesNotFoundException(List<String> dependencies) {
super("Dependencies '{}' not found", dependencies);
this.dependencies = dependencies;
}
public List<String> getDependencies() {
return dependencies;
}
} }
private PluginWrapper getPlugin(String pluginId) throws PluginNotFoundException { /**
for (PluginWrapper pluginWrapper : plugins) { * Indicates that some dependencies have wrong version.
if (pluginId.equals(pluginWrapper.getDescriptor().getPluginId())) { */
return pluginWrapper; public static class DependenciesWrongVersionException extends PluginException {
}
} private List<WrongDependencyVersion> dependencies;
public DependenciesWrongVersionException(List<WrongDependencyVersion> dependencies) {
super("Dependencies '{}' have wrong version", dependencies);
throw new PluginNotFoundException(pluginId); this.dependencies = dependencies;
} }
public List<WrongDependencyVersion> getDependencies() {
return dependencies;
}
}
} }

1
pf4j/src/main/java/ro/fortsoft/pf4j/PluginDependency.java

@ -28,7 +28,6 @@ public class PluginDependency {
if (index == -1) { if (index == -1) {
this.pluginId = dependency; this.pluginId = dependency;
} else { } else {
this.pluginId = dependency.substring(0, index); this.pluginId = dependency.substring(0, index);
if (dependency.length() > index + 1) { if (dependency.length() > index + 1) {
this.pluginVersionSupport = dependency.substring(index + 1); this.pluginVersionSupport = dependency.substring(index + 1);

32
pf4j/src/main/java/ro/fortsoft/pf4j/PluginDescriptor.java

@ -109,31 +109,43 @@ public class PluginDescriptor {
+ license + "]"; + license + "]";
} }
void setPluginId(String pluginId) { PluginDescriptor setPluginId(String pluginId) {
this.pluginId = pluginId; this.pluginId = pluginId;
return this;
} }
void setPluginDescription(String pluginDescription) { PluginDescriptor setPluginDescription(String pluginDescription) {
this.pluginDescription = pluginDescription; this.pluginDescription = pluginDescription;
return this;
} }
void setPluginClass(String pluginClassName) { PluginDescriptor setPluginClass(String pluginClassName) {
this.pluginClass = pluginClassName; this.pluginClass = pluginClassName;
return this;
} }
void setPluginVersion(Version version) { PluginDescriptor setPluginVersion(Version version) {
this.version = version; this.version = version;
return this;
} }
void setProvider(String provider) { PluginDescriptor setProvider(String provider) {
this.provider = provider; this.provider = provider;
return this;
} }
void setRequires(String requires) { PluginDescriptor setRequires(String requires) {
this.requires = requires; this.requires = requires;
return this;
} }
void setDependencies(String dependencies) { PluginDescriptor setDependencies(String dependencies) {
if (dependencies != null) { if (dependencies != null) {
dependencies = dependencies.trim(); dependencies = dependencies.trim();
if (dependencies.isEmpty()) { if (dependencies.isEmpty()) {
@ -154,10 +166,14 @@ public class PluginDescriptor {
} else { } else {
this.dependencies = Collections.emptyList(); this.dependencies = Collections.emptyList();
} }
return this;
} }
public void setLicense(String license) { public PluginDescriptor setLicense(String license) {
this.license = license; this.license = license;
return this;
} }
} }

2
pf4j/src/main/java/ro/fortsoft/pf4j/PluginException.java

@ -24,8 +24,6 @@ import ro.fortsoft.pf4j.util.StringUtils;
*/ */
public class PluginException extends Exception { public class PluginException extends Exception {
private static final long serialVersionUID = 1L;
public PluginException() { public PluginException() {
super(); super();
} }

37
pf4j/src/main/java/ro/fortsoft/pf4j/PluginNotFoundException.java

@ -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;
}
}

38
pf4j/src/main/java/ro/fortsoft/pf4j/util/DirectedGraph.java

@ -23,6 +23,8 @@ import java.util.Map;
import java.util.Stack; import java.util.Stack;
/** /**
* See <a href="https://en.wikipedia.org/wiki/Directed_graph">Wikipedia</a> for more information.
*
* @author Decebal Suiu * @author Decebal Suiu
*/ */
public class DirectedGraph<V> { public class DirectedGraph<V> {
@ -38,7 +40,7 @@ public class DirectedGraph<V> {
* Add a vertex to the graph. Nothing happens if vertex is already in graph. * Add a vertex to the graph. Nothing happens if vertex is already in graph.
*/ */
public void addVertex(V vertex) { public void addVertex(V vertex) {
if (neighbors.containsKey(vertex)) { if (containsVertex(vertex)) {
return; return;
} }
@ -52,38 +54,42 @@ public class DirectedGraph<V> {
return neighbors.containsKey(vertex); 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. * 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. * This implementation allows the creation of multi-edges and self-loops.
*/ */
public void addEdge(V from, V to) { public void addEdge(V from, V to) {
this.addVertex(from); addVertex(from);
this.addVertex(to); addVertex(to);
neighbors.get(from).add(to); neighbors.get(from).add(to);
} }
/** /**
* Remove an edge from the graph. Nothing happens if no such edge. * 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) { public void removeEdge(V from, V to) {
if (!(this.containsVertex(from) && this.containsVertex(to))) { if (!containsVertex(from)) {
throw new IllegalArgumentException("Nonexistent vertex"); throw new IllegalArgumentException("Nonexistent vertex " + from);
}
if (!containsVertex(to)) {
throw new IllegalArgumentException("Nonexistent vertex " + to);
} }
neighbors.get(from).remove(to); neighbors.get(from).remove(to);
} }
public List<V> getNeighbors(V vertex) { public List<V> getNeighbors(V vertex) {
if (!neighbors.containsKey(vertex)) { return containsVertex(vertex) ? neighbors.get(vertex) : new ArrayList<V>();
return new ArrayList<V>();
}
return neighbors.get(vertex);
} }
/** /**
* 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<V, Integer> outDegree() { public Map<V, Integer> outDegree() {
Map<V, Integer> result = new HashMap<>(); Map<V, Integer> result = new HashMap<>();
@ -95,9 +101,9 @@ public class DirectedGraph<V> {
} }
/** /**
* 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<V,Integer> inDegree() { public Map<V, Integer> inDegree() {
Map<V, Integer> result = new HashMap<>(); Map<V, Integer> result = new HashMap<>();
for (V vertex : neighbors.keySet()) { for (V vertex : neighbors.keySet()) {
result.put(vertex, 0); // all in-degrees are 0 result.put(vertex, 0); // all in-degrees are 0
@ -113,6 +119,7 @@ public class DirectedGraph<V> {
/** /**
* Report (as a List) the topological sort of the vertices; null for no such sort. * Report (as a List) the topological sort of the vertices; null for no such sort.
* See <a href="https://en.wikipedia.org/wiki/Topological_sorting">this</a> for more information.
*/ */
public List<V> topologicalSort() { public List<V> topologicalSort() {
Map<V, Integer> degree = inDegree(); Map<V, Integer> degree = inDegree();
@ -156,6 +163,7 @@ public class DirectedGraph<V> {
if (list == null) { if (list == null) {
return null; return null;
} }
Collections.reverse(list); Collections.reverse(list);
return list; return list;

19
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.Logger;
import org.slf4j.LoggerFactory; 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.FileVisitResult;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; 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.BasicFileAttributes;
import java.nio.file.attribute.FileTime; import java.nio.file.attribute.FileTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@ -34,9 +39,8 @@ import java.util.List;
* @author Decebal Suiu * @author Decebal Suiu
*/ */
public class FileUtils { public class FileUtils {
private static final Logger log = LoggerFactory.getLogger(FileUtils.class);
private static final List<String> ZIP_EXTENSIONS = Arrays.asList(".zip", ".ZIP", ".Zip"); private static final Logger log = LoggerFactory.getLogger(FileUtils.class);
public static List<String> readLines(File file, boolean ignoreComments) throws IOException { public static List<String> readLines(File file, boolean ignoreComments) throws IOException {
if (!file.exists() || !file.isFile()) { if (!file.exists() || !file.isFile()) {
@ -86,19 +90,23 @@ public class FileUtils {
*/ */
public static void delete(Path path) throws IOException { public static void delete(Path path) throws IOException {
Files.walkFileTree(path, new SimpleFileVisitor<Path>() { Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
@Override @Override
public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException { public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException {
if (!attrs.isSymbolicLink()) { if (!attrs.isSymbolicLink()) {
Files.delete(path); Files.delete(path);
} }
return FileVisitResult.CONTINUE; return FileVisitResult.CONTINUE;
} }
@Override @Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir); Files.delete(dir);
return FileVisitResult.CONTINUE; return FileVisitResult.CONTINUE;
} }
}); });
} }
@ -140,6 +148,7 @@ public class FileUtils {
return newPath; return newPath;
} }
} }
return null; return null;
} }
@ -151,6 +160,7 @@ public class FileUtils {
if (path == null) { if (path == null) {
return; return;
} }
try { try {
Files.delete(path); Files.delete(path);
} catch (IOException ignored) { } } catch (IOException ignored) { }
@ -202,4 +212,5 @@ public class FileUtils {
public static boolean isZipFile(Path path) { public static boolean isZipFile(Path path) {
return Files.isRegularFile(path) && path.toString().toLowerCase().endsWith(".zip"); return Files.isRegularFile(path) && path.toString().toLowerCase().endsWith(".zip");
} }
} }

141
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<PluginDescriptor> 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<PluginDescriptor> 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<PluginDescriptor> 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<PluginDescriptor> 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<PluginDescriptor> plugins = new ArrayList<>();
plugins.add(pd1);
plugins.add(pd2);
DependencyResolver resolver = new DependencyResolver();
DependencyResolver.Result result = resolver.resolve(plugins);
assertTrue(result.getWrongVersionDependencies().isEmpty());
}
}
Loading…
Cancel
Save