Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

DependencyResolver.java 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  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. * <p>
  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 final 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. /**
  47. * Resolve the dependencies for the given plugins.
  48. *
  49. * @param plugins the list of plugins
  50. * @return a {@link Result} object
  51. */
  52. public Result resolve(List<PluginDescriptor> plugins) {
  53. // create graphs
  54. dependenciesGraph = new DirectedGraph<>();
  55. dependentsGraph = new DirectedGraph<>();
  56. // populate graphs
  57. Map<String, PluginDescriptor> pluginByIds = new HashMap<>();
  58. for (PluginDescriptor plugin : plugins) {
  59. addPlugin(plugin);
  60. pluginByIds.put(plugin.getPluginId(), plugin);
  61. }
  62. log.debug("Graph: {}", dependenciesGraph);
  63. // get a sorted list of dependencies
  64. List<String> sortedPlugins = dependenciesGraph.reverseTopologicalSort();
  65. log.debug("Plugins order: {}", sortedPlugins);
  66. // create the result object
  67. Result result = new Result(sortedPlugins);
  68. resolved = true;
  69. if (sortedPlugins != null) { // no cyclic dependency
  70. // detect not found dependencies
  71. for (String pluginId : sortedPlugins) {
  72. if (!pluginByIds.containsKey(pluginId)) {
  73. result.addNotFoundDependency(pluginId);
  74. }
  75. }
  76. }
  77. // check dependencies versions
  78. for (PluginDescriptor plugin : plugins) {
  79. String pluginId = plugin.getPluginId();
  80. String existingVersion = plugin.getVersion();
  81. List<String> dependents = getDependents(pluginId);
  82. while (!dependents.isEmpty()) {
  83. String dependentId = dependents.remove(0);
  84. PluginDescriptor dependent = pluginByIds.get(dependentId);
  85. String requiredVersion = getDependencyVersionSupport(dependent, pluginId);
  86. boolean ok = checkDependencyVersion(requiredVersion, existingVersion);
  87. if (!ok) {
  88. result.addWrongDependencyVersion(new WrongDependencyVersion(pluginId, dependentId, existingVersion, requiredVersion));
  89. }
  90. }
  91. }
  92. return result;
  93. }
  94. /**
  95. * Retrieves the plugins ids that the given plugin id directly depends on.
  96. *
  97. * @param pluginId the unique plugin identifier, specified in its metadata
  98. * @return an immutable list of dependencies (new list for each call)
  99. */
  100. public List<String> getDependencies(String pluginId) {
  101. checkResolved();
  102. return new ArrayList<>(dependenciesGraph.getNeighbors(pluginId));
  103. }
  104. /**
  105. * Retrieves the plugins ids that the given content is a direct dependency of.
  106. *
  107. * @param pluginId the unique plugin identifier, specified in its metadata
  108. * @return an immutable list of dependents (new list for each call)
  109. */
  110. public List<String> getDependents(String pluginId) {
  111. checkResolved();
  112. return new ArrayList<>(dependentsGraph.getNeighbors(pluginId));
  113. }
  114. /**
  115. * Check if an existing version of dependency is compatible with the required version (from plugin descriptor).
  116. *
  117. * @param requiredVersion the required version
  118. * @param existingVersion the existing version
  119. * @return {@code true} if the existing version is compatible with the required version, {@code false} otherwise
  120. */
  121. protected boolean checkDependencyVersion(String requiredVersion, String existingVersion) {
  122. return versionManager.checkVersionConstraint(existingVersion, requiredVersion);
  123. }
  124. private void addPlugin(PluginDescriptor descriptor) {
  125. String pluginId = descriptor.getPluginId();
  126. List<PluginDependency> dependencies = descriptor.getDependencies();
  127. if (dependencies.isEmpty()) {
  128. dependenciesGraph.addVertex(pluginId);
  129. dependentsGraph.addVertex(pluginId);
  130. } else {
  131. boolean edgeAdded = false;
  132. for (PluginDependency dependency : dependencies) {
  133. // Don't register optional plugins in the dependency graph to avoid automatic disabling of the plugin,
  134. // if an optional dependency is missing.
  135. if (!dependency.isOptional()) {
  136. edgeAdded = true;
  137. dependenciesGraph.addEdge(pluginId, dependency.getPluginId());
  138. dependentsGraph.addEdge(dependency.getPluginId(), pluginId);
  139. }
  140. }
  141. // Register the plugin without dependencies, if all of its dependencies are optional.
  142. if (!edgeAdded) {
  143. dependenciesGraph.addVertex(pluginId);
  144. dependentsGraph.addVertex(pluginId);
  145. }
  146. }
  147. }
  148. private void checkResolved() {
  149. if (!resolved) {
  150. throw new IllegalStateException("Call 'resolve' method first");
  151. }
  152. }
  153. private String getDependencyVersionSupport(PluginDescriptor dependent, String dependencyId) {
  154. List<PluginDependency> dependencies = dependent.getDependencies();
  155. for (PluginDependency dependency : dependencies) {
  156. if (dependencyId.equals(dependency.getPluginId())) {
  157. return dependency.getPluginVersionSupport();
  158. }
  159. }
  160. throw new IllegalStateException("Cannot find a dependency with id '" + dependencyId +
  161. "' for plugin '" + dependent.getPluginId() + "'");
  162. }
  163. /**
  164. * The result of the {@link #resolve(List)} operation.
  165. */
  166. public static class Result {
  167. private boolean cyclicDependency;
  168. private final List<String> notFoundDependencies; // value is "pluginId"
  169. private final List<String> sortedPlugins; // value is "pluginId"
  170. private final List<WrongDependencyVersion> wrongVersionDependencies;
  171. Result(List<String> sortedPlugins) {
  172. if (sortedPlugins == null) {
  173. cyclicDependency = true;
  174. this.sortedPlugins = Collections.emptyList();
  175. } else {
  176. this.sortedPlugins = new ArrayList<>(sortedPlugins);
  177. }
  178. notFoundDependencies = new ArrayList<>();
  179. wrongVersionDependencies = new ArrayList<>();
  180. }
  181. /**
  182. * Returns true is a cyclic dependency was detected.
  183. *
  184. * @return true is a cyclic dependency was detected
  185. */
  186. public boolean hasCyclicDependency() {
  187. return cyclicDependency;
  188. }
  189. /**
  190. * Returns true if there are dependencies required that were not found.
  191. *
  192. * @return true if there are dependencies required that were not found
  193. */
  194. public boolean hasNotFoundDependencies() {
  195. return !notFoundDependencies.isEmpty();
  196. }
  197. /**
  198. * Returns a list with dependencies required that were not found.
  199. *
  200. * @return a list with dependencies required that were not found
  201. */
  202. public List<String> getNotFoundDependencies() {
  203. return notFoundDependencies;
  204. }
  205. /**
  206. * Returns true if there are dependencies with wrong version.
  207. *
  208. * @return true if there are dependencies with wrong version
  209. */
  210. public boolean hasWrongVersionDependencies() {
  211. return !wrongVersionDependencies.isEmpty();
  212. }
  213. /**
  214. * Returns a list with dependencies with wrong version.
  215. *
  216. * @return a list with dependencies with wrong version
  217. */
  218. public List<WrongDependencyVersion> getWrongVersionDependencies() {
  219. return wrongVersionDependencies;
  220. }
  221. /**
  222. * Returns true if the result is OK (no cyclic dependency, no not found dependencies, no wrong version dependencies).
  223. *
  224. * @return true if the result is OK
  225. */
  226. public boolean isOK() {
  227. return !hasCyclicDependency() && !hasNotFoundDependencies() && !hasWrongVersionDependencies();
  228. }
  229. /**
  230. * Get the list of plugins in dependency sorted order.
  231. *
  232. * @return the list of plugins in dependency sorted order
  233. */
  234. public List<String> getSortedPlugins() {
  235. return sortedPlugins;
  236. }
  237. void addNotFoundDependency(String pluginId) {
  238. notFoundDependencies.add(pluginId);
  239. }
  240. void addWrongDependencyVersion(WrongDependencyVersion wrongDependencyVersion) {
  241. wrongVersionDependencies.add(wrongDependencyVersion);
  242. }
  243. }
  244. /**
  245. * Represents a wrong dependency version.
  246. */
  247. public static class WrongDependencyVersion {
  248. private final String dependencyId; // value is "pluginId"
  249. private final String dependentId; // value is "pluginId"
  250. private final String existingVersion;
  251. private final String requiredVersion;
  252. WrongDependencyVersion(String dependencyId, String dependentId, String existingVersion, String requiredVersion) {
  253. this.dependencyId = dependencyId;
  254. this.dependentId = dependentId;
  255. this.existingVersion = existingVersion;
  256. this.requiredVersion = requiredVersion;
  257. }
  258. public String getDependencyId() {
  259. return dependencyId;
  260. }
  261. public String getDependentId() {
  262. return dependentId;
  263. }
  264. public String getExistingVersion() {
  265. return existingVersion;
  266. }
  267. public String getRequiredVersion() {
  268. return requiredVersion;
  269. }
  270. @Override
  271. public String toString() {
  272. return "WrongDependencyVersion{" +
  273. "dependencyId='" + dependencyId + '\'' +
  274. ", dependentId='" + dependentId + '\'' +
  275. ", existingVersion='" + existingVersion + '\'' +
  276. ", requiredVersion='" + requiredVersion + '\'' +
  277. '}';
  278. }
  279. }
  280. /**
  281. * It will be thrown if a cyclic dependency is detected.
  282. */
  283. public static class CyclicDependencyException extends PluginRuntimeException {
  284. public CyclicDependencyException() {
  285. super("Cyclic dependencies");
  286. }
  287. }
  288. /**
  289. * Indicates that the dependencies required were not found.
  290. */
  291. public static class DependenciesNotFoundException extends PluginRuntimeException {
  292. private final List<String> dependencies;
  293. public DependenciesNotFoundException(List<String> dependencies) {
  294. super("Dependencies '{}' not found", dependencies);
  295. this.dependencies = dependencies;
  296. }
  297. public List<String> getDependencies() {
  298. return dependencies;
  299. }
  300. }
  301. /**
  302. * Indicates that some dependencies have wrong version.
  303. */
  304. public static class DependenciesWrongVersionException extends PluginRuntimeException {
  305. private final List<WrongDependencyVersion> dependencies;
  306. public DependenciesWrongVersionException(List<WrongDependencyVersion> dependencies) {
  307. super("Dependencies '{}' have wrong version", dependencies);
  308. this.dependencies = dependencies;
  309. }
  310. public List<WrongDependencyVersion> getDependencies() {
  311. return dependencies;
  312. }
  313. }
  314. }