You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

DependencyResolver.java 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. /*
  2. * Copyright (C) 2012-present the original author or authors.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.pf4j;
  17. import org.slf4j.Logger;
  18. import org.slf4j.LoggerFactory;
  19. import org.pf4j.util.DirectedGraph;
  20. import java.util.ArrayList;
  21. import java.util.Collections;
  22. import java.util.HashMap;
  23. import java.util.List;
  24. import java.util.Map;
  25. /**
  26. * This class builds a dependency graph for a list of plugins (descriptors).
  27. * The entry point is the {@link #resolve(List)} method, method that returns a {@link Result} object.
  28. * The {@code Result} class contains nice information about the result of resolve operation (if it's a cyclic dependency,
  29. * they are not found dependencies, they are dependencies with wrong version).
  30. * This class is very useful for if-else scenarios.
  31. *
  32. * Only some attributes (pluginId, dependencies and pluginVersion) from {@link PluginDescriptor} are used in
  33. * the process of {@code resolve} operation.
  34. *
  35. * @author Decebal Suiu
  36. */
  37. public class DependencyResolver {
  38. private static final Logger log = LoggerFactory.getLogger(DependencyResolver.class);
  39. private VersionManager versionManager;
  40. private DirectedGraph<String> dependenciesGraph; // the value is 'pluginId'
  41. private DirectedGraph<String> dependentsGraph; // the value is 'pluginId'
  42. private boolean resolved;
  43. public DependencyResolver(VersionManager versionManager) {
  44. this.versionManager = versionManager;
  45. }
  46. public Result resolve(List<PluginDescriptor> plugins) {
  47. // create graphs
  48. dependenciesGraph = new DirectedGraph<>();
  49. dependentsGraph = new DirectedGraph<>();
  50. // populate graphs
  51. Map<String, PluginDescriptor> pluginByIds = new HashMap<>();
  52. for (PluginDescriptor plugin : plugins) {
  53. addPlugin(plugin);
  54. pluginByIds.put(plugin.getPluginId(), plugin);
  55. }
  56. log.debug("Graph: {}", dependenciesGraph);
  57. // get a sorted list of dependencies
  58. List<String> sortedPlugins = dependenciesGraph.reverseTopologicalSort();
  59. log.debug("Plugins order: {}", sortedPlugins);
  60. // create the result object
  61. Result result = new Result(sortedPlugins);
  62. resolved = true;
  63. if (sortedPlugins != null) { // no cyclic dependency
  64. // detect not found dependencies
  65. for (String pluginId : sortedPlugins) {
  66. if (!pluginByIds.containsKey(pluginId)) {
  67. result.addNotFoundDependency(pluginId);
  68. }
  69. }
  70. }
  71. // check dependencies versions
  72. for (PluginDescriptor plugin : plugins) {
  73. String pluginId = plugin.getPluginId();
  74. String existingVersion = plugin.getVersion();
  75. List<String> dependents = new ArrayList<>(getDependents(pluginId));
  76. while (!dependents.isEmpty()) {
  77. String dependentId = dependents.remove(0);
  78. PluginDescriptor dependent = pluginByIds.get(dependentId);
  79. String requiredVersion = getDependencyVersionSupport(dependent, pluginId);
  80. boolean ok = checkDependencyVersion(requiredVersion, existingVersion);
  81. if (!ok) {
  82. result.addWrongDependencyVersion(new WrongDependencyVersion(pluginId, dependentId, existingVersion, requiredVersion));
  83. }
  84. }
  85. }
  86. return result;
  87. }
  88. /**
  89. * Retrieves the plugins ids that the given plugin id directly depends on.
  90. *
  91. * @param pluginId
  92. * @return
  93. */
  94. public List<String> getDependencies(String pluginId) {
  95. checkResolved();
  96. return dependenciesGraph.getNeighbors(pluginId);
  97. }
  98. /**
  99. * Retrieves the plugins ids that the given content is a direct dependency of.
  100. *
  101. * @param pluginId
  102. * @return
  103. */
  104. public List<String> getDependents(String pluginId) {
  105. checkResolved();
  106. return dependentsGraph.getNeighbors(pluginId);
  107. }
  108. /**
  109. * Check if an existing version of dependency is compatible with the required version (from plugin descriptor).
  110. *
  111. * @param requiredVersion
  112. * @param existingVersion
  113. * @return
  114. */
  115. protected boolean checkDependencyVersion(String requiredVersion, String existingVersion) {
  116. return versionManager.checkVersionConstraint(existingVersion, requiredVersion);
  117. }
  118. private void addPlugin(PluginDescriptor descriptor) {
  119. String pluginId = descriptor.getPluginId();
  120. List<PluginDependency> dependencies = descriptor.getDependencies();
  121. if (dependencies.isEmpty()) {
  122. dependenciesGraph.addVertex(pluginId);
  123. dependentsGraph.addVertex(pluginId);
  124. } else {
  125. boolean edgeAdded = false;
  126. for (PluginDependency dependency : dependencies) {
  127. // Don't register optional plugins in the dependency graph
  128. // to avoid automatic disabling of the plugin,
  129. // if an optional dependency is missing.
  130. if (!dependency.isOptional()) {
  131. edgeAdded = true;
  132. dependenciesGraph.addEdge(pluginId, dependency.getPluginId());
  133. dependentsGraph.addEdge(dependency.getPluginId(), pluginId);
  134. }
  135. }
  136. // Register the plugin without dependencies,
  137. // if all of its dependencies are optional.
  138. if (!edgeAdded) {
  139. dependenciesGraph.addVertex(pluginId);
  140. dependentsGraph.addVertex(pluginId);
  141. }
  142. }
  143. }
  144. private void checkResolved() {
  145. if (!resolved) {
  146. throw new IllegalStateException("Call 'resolve' method first");
  147. }
  148. }
  149. private String getDependencyVersionSupport(PluginDescriptor dependent, String dependencyId) {
  150. List<PluginDependency> dependencies = dependent.getDependencies();
  151. for (PluginDependency dependency : dependencies) {
  152. if (dependencyId.equals(dependency.getPluginId())) {
  153. return dependency.getPluginVersionSupport();
  154. }
  155. }
  156. throw new IllegalStateException("Cannot find a dependency with id '" + dependencyId +
  157. "' for plugin '" + dependent.getPluginId() + "'");
  158. }
  159. public static class Result {
  160. private boolean cyclicDependency;
  161. private List<String> notFoundDependencies; // value is "pluginId"
  162. private List<String> sortedPlugins; // value is "pluginId"
  163. private List<WrongDependencyVersion> wrongVersionDependencies;
  164. Result(List<String> sortedPlugins) {
  165. if (sortedPlugins == null) {
  166. cyclicDependency = true;
  167. this.sortedPlugins = Collections.emptyList();
  168. } else {
  169. this.sortedPlugins = new ArrayList<>(sortedPlugins);
  170. }
  171. notFoundDependencies = new ArrayList<>();
  172. wrongVersionDependencies = new ArrayList<>();
  173. }
  174. /**
  175. * Returns true is a cyclic dependency was detected.
  176. */
  177. public boolean hasCyclicDependency() {
  178. return cyclicDependency;
  179. }
  180. /**
  181. * Returns a list with dependencies required that were not found.
  182. */
  183. public List<String> getNotFoundDependencies() {
  184. return notFoundDependencies;
  185. }
  186. /**
  187. * Returns a list with dependencies with wrong version.
  188. */
  189. public List<WrongDependencyVersion> getWrongVersionDependencies() {
  190. return wrongVersionDependencies;
  191. }
  192. /**
  193. * Get the list of plugins in dependency sorted order.
  194. */
  195. public List<String> getSortedPlugins() {
  196. return sortedPlugins;
  197. }
  198. void addNotFoundDependency(String pluginId) {
  199. notFoundDependencies.add(pluginId);
  200. }
  201. void addWrongDependencyVersion(WrongDependencyVersion wrongDependencyVersion) {
  202. wrongVersionDependencies.add(wrongDependencyVersion);
  203. }
  204. }
  205. public static class WrongDependencyVersion {
  206. private String dependencyId; // value is "pluginId"
  207. private String dependentId; // value is "pluginId"
  208. private String existingVersion;
  209. private String requiredVersion;
  210. WrongDependencyVersion(String dependencyId, String dependentId, String existingVersion, String requiredVersion) {
  211. this.dependencyId = dependencyId;
  212. this.dependentId = dependentId;
  213. this.existingVersion = existingVersion;
  214. this.requiredVersion = requiredVersion;
  215. }
  216. public String getDependencyId() {
  217. return dependencyId;
  218. }
  219. public String getDependentId() {
  220. return dependentId;
  221. }
  222. public String getExistingVersion() {
  223. return existingVersion;
  224. }
  225. public String getRequiredVersion() {
  226. return requiredVersion;
  227. }
  228. }
  229. /**
  230. * It will be thrown if a cyclic dependency is detected.
  231. */
  232. public static class CyclicDependencyException extends PluginException {
  233. public CyclicDependencyException() {
  234. super("Cyclic dependencies");
  235. }
  236. }
  237. /**
  238. * Indicates that the dependencies required were not found.
  239. */
  240. public static class DependenciesNotFoundException extends PluginException {
  241. private List<String> dependencies;
  242. public DependenciesNotFoundException(List<String> dependencies) {
  243. super("Dependencies '{}' not found", dependencies);
  244. this.dependencies = dependencies;
  245. }
  246. public List<String> getDependencies() {
  247. return dependencies;
  248. }
  249. }
  250. /**
  251. * Indicates that some dependencies have wrong version.
  252. */
  253. public static class DependenciesWrongVersionException extends PluginException {
  254. private List<WrongDependencyVersion> dependencies;
  255. public DependenciesWrongVersionException(List<WrongDependencyVersion> dependencies) {
  256. super("Dependencies '{}' have wrong version", dependencies);
  257. this.dependencies = dependencies;
  258. }
  259. public List<WrongDependencyVersion> getDependencies() {
  260. return dependencies;
  261. }
  262. }
  263. }