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 9.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. /*
  2. * Copyright 2012 Decebal Suiu
  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 = 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.satisfies(requiredVersion, existingVersion);
  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. for (PluginDependency dependency : dependencies) {
  126. dependenciesGraph.addEdge(pluginId, dependency.getPluginId());
  127. dependentsGraph.addEdge(dependency.getPluginId(), pluginId);
  128. }
  129. }
  130. }
  131. private void checkResolved() {
  132. if (!resolved) {
  133. throw new IllegalStateException("Call 'resolve' method first");
  134. }
  135. }
  136. private String getDependencyVersionSupport(PluginDescriptor dependent, String dependencyId) {
  137. List<PluginDependency> dependencies = dependent.getDependencies();
  138. for (PluginDependency dependency : dependencies) {
  139. if (dependencyId.equals(dependency.getPluginId())) {
  140. return dependency.getPluginVersionSupport();
  141. }
  142. }
  143. throw new IllegalStateException("Cannot find a dependency with id '" + dependencyId +
  144. "' for plugin '" + dependent.getPluginId() + "'");
  145. }
  146. public static class Result {
  147. private boolean cyclicDependency;
  148. private List<String> notFoundDependencies; // value is "pluginId"
  149. private List<String> sortedPlugins; // value is "pluginId"
  150. private List<WrongDependencyVersion> wrongVersionDependencies;
  151. Result(List<String> sortedPlugins) {
  152. if (sortedPlugins == null) {
  153. cyclicDependency = true;
  154. this.sortedPlugins = Collections.emptyList();
  155. } else {
  156. this.sortedPlugins = new ArrayList<>(sortedPlugins);
  157. }
  158. notFoundDependencies = new ArrayList<>();
  159. wrongVersionDependencies = new ArrayList<>();
  160. }
  161. /**
  162. * Returns true is a cyclic dependency was detected.
  163. */
  164. public boolean hasCyclicDependency() {
  165. return cyclicDependency;
  166. }
  167. /**
  168. * Returns a list with dependencies required that were not found.
  169. */
  170. public List<String> getNotFoundDependencies() {
  171. return notFoundDependencies;
  172. }
  173. /**
  174. * Returns a list with dependencies with wrong version.
  175. */
  176. public List<WrongDependencyVersion> getWrongVersionDependencies() {
  177. return wrongVersionDependencies;
  178. }
  179. /**
  180. * Get the list of plugins in dependency sorted order.
  181. */
  182. public List<String> getSortedPlugins() {
  183. return sortedPlugins;
  184. }
  185. void addNotFoundDependency(String pluginId) {
  186. notFoundDependencies.add(pluginId);
  187. }
  188. void addWrongDependencyVersion(WrongDependencyVersion wrongDependencyVersion) {
  189. wrongVersionDependencies.add(wrongDependencyVersion);
  190. }
  191. }
  192. public static class WrongDependencyVersion {
  193. private String dependencyId; // value is "pluginId"
  194. private String dependentId; // value is "pluginId"
  195. private String existingVersion;
  196. private String requiredVersion;
  197. WrongDependencyVersion(String dependencyId, String dependentId, String existingVersion, String requiredVersion) {
  198. this.dependencyId = dependencyId;
  199. this.dependentId = dependentId;
  200. this.existingVersion = existingVersion;
  201. this.requiredVersion = requiredVersion;
  202. }
  203. public String getDependencyId() {
  204. return dependencyId;
  205. }
  206. public String getDependentId() {
  207. return dependentId;
  208. }
  209. public String getExistingVersion() {
  210. return existingVersion;
  211. }
  212. public String getRequiredVersion() {
  213. return requiredVersion;
  214. }
  215. }
  216. /**
  217. * It will be thrown if a cyclic dependency is detected.
  218. */
  219. public static class CyclicDependencyException extends PluginException {
  220. public CyclicDependencyException() {
  221. super("Cyclic dependencies");
  222. }
  223. }
  224. /**
  225. * Indicates that the dependencies required were not found.
  226. */
  227. public static class DependenciesNotFoundException extends PluginException {
  228. private List<String> dependencies;
  229. public DependenciesNotFoundException(List<String> dependencies) {
  230. super("Dependencies '{}' not found", dependencies);
  231. this.dependencies = dependencies;
  232. }
  233. public List<String> getDependencies() {
  234. return dependencies;
  235. }
  236. }
  237. /**
  238. * Indicates that some dependencies have wrong version.
  239. */
  240. public static class DependenciesWrongVersionException extends PluginException {
  241. private List<WrongDependencyVersion> dependencies;
  242. public DependenciesWrongVersionException(List<WrongDependencyVersion> dependencies) {
  243. super("Dependencies '{}' have wrong version", dependencies);
  244. this.dependencies = dependencies;
  245. }
  246. public List<WrongDependencyVersion> getDependencies() {
  247. return dependencies;
  248. }
  249. }
  250. }