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.

PluginInfo.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2019 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.core.platform;
  21. import com.google.common.annotations.VisibleForTesting;
  22. import com.google.common.base.Joiner;
  23. import com.google.common.collect.ComparisonChain;
  24. import com.google.common.collect.Ordering;
  25. import java.io.File;
  26. import java.io.IOException;
  27. import java.util.Arrays;
  28. import java.util.HashSet;
  29. import java.util.Optional;
  30. import java.util.Set;
  31. import java.util.jar.JarFile;
  32. import java.util.regex.Pattern;
  33. import java.util.zip.ZipEntry;
  34. import javax.annotation.CheckForNull;
  35. import javax.annotation.Nullable;
  36. import org.apache.commons.lang.StringUtils;
  37. import org.sonar.api.utils.MessageException;
  38. import org.sonar.api.utils.log.Logger;
  39. import org.sonar.api.utils.log.Loggers;
  40. import org.sonar.updatecenter.common.PluginManifest;
  41. import org.sonar.updatecenter.common.Version;
  42. import static java.util.Objects.requireNonNull;
  43. public class PluginInfo implements Comparable<PluginInfo> {
  44. private static final Logger LOGGER = Loggers.get(PluginInfo.class);
  45. private static final Joiner SLASH_JOINER = Joiner.on(" / ").skipNulls();
  46. public static class RequiredPlugin {
  47. private static final Pattern PARSER = Pattern.compile("\\w+:.+");
  48. private final String key;
  49. private final Version minimalVersion;
  50. public RequiredPlugin(String key, Version minimalVersion) {
  51. this.key = key;
  52. this.minimalVersion = minimalVersion;
  53. }
  54. public String getKey() {
  55. return key;
  56. }
  57. public Version getMinimalVersion() {
  58. return minimalVersion;
  59. }
  60. public static RequiredPlugin parse(String s) {
  61. if (!PARSER.matcher(s).matches()) {
  62. throw new IllegalArgumentException("Manifest field does not have correct format: " + s);
  63. }
  64. String[] fields = StringUtils.split(s, ':');
  65. return new RequiredPlugin(fields[0], Version.create(fields[1]).removeQualifier());
  66. }
  67. @Override
  68. public boolean equals(Object o) {
  69. if (this == o) {
  70. return true;
  71. }
  72. if (o == null || getClass() != o.getClass()) {
  73. return false;
  74. }
  75. RequiredPlugin that = (RequiredPlugin) o;
  76. return key.equals(that.key);
  77. }
  78. @Override
  79. public int hashCode() {
  80. return key.hashCode();
  81. }
  82. @Override
  83. public String toString() {
  84. return new StringBuilder().append(key).append(':').append(minimalVersion.getName()).toString();
  85. }
  86. }
  87. private final String key;
  88. private String name;
  89. @CheckForNull
  90. private File jarFile;
  91. @CheckForNull
  92. private String mainClass;
  93. @CheckForNull
  94. private Version version;
  95. private String displayVersion;
  96. @CheckForNull
  97. private Version minimalSqVersion;
  98. @CheckForNull
  99. private String description;
  100. @CheckForNull
  101. private String organizationName;
  102. @CheckForNull
  103. private String organizationUrl;
  104. @CheckForNull
  105. private String license;
  106. @CheckForNull
  107. private String homepageUrl;
  108. @CheckForNull
  109. private String issueTrackerUrl;
  110. private boolean useChildFirstClassLoader;
  111. @CheckForNull
  112. private String basePlugin;
  113. @CheckForNull
  114. private String implementationBuild;
  115. @CheckForNull
  116. private boolean sonarLintSupported;
  117. @CheckForNull
  118. private String documentationPath;
  119. private final Set<RequiredPlugin> requiredPlugins = new HashSet<>();
  120. public PluginInfo(String key) {
  121. requireNonNull(key, "Plugin key is missing from manifest");
  122. this.key = key;
  123. this.name = key;
  124. }
  125. public PluginInfo setJarFile(@Nullable File f) {
  126. this.jarFile = f;
  127. return this;
  128. }
  129. @CheckForNull
  130. public File getJarFile() {
  131. return jarFile;
  132. }
  133. public File getNonNullJarFile() {
  134. requireNonNull(jarFile);
  135. return jarFile;
  136. }
  137. public String getKey() {
  138. return key;
  139. }
  140. public String getName() {
  141. return name;
  142. }
  143. @CheckForNull
  144. public Version getVersion() {
  145. return version;
  146. }
  147. @CheckForNull
  148. public String getDisplayVersion() {
  149. return displayVersion;
  150. }
  151. public PluginInfo setDisplayVersion(@Nullable String displayVersion) {
  152. this.displayVersion = displayVersion;
  153. return this;
  154. }
  155. @CheckForNull
  156. public Version getMinimalSqVersion() {
  157. return minimalSqVersion;
  158. }
  159. @CheckForNull
  160. public String getMainClass() {
  161. return mainClass;
  162. }
  163. @CheckForNull
  164. public String getDescription() {
  165. return description;
  166. }
  167. @CheckForNull
  168. public String getOrganizationName() {
  169. return organizationName;
  170. }
  171. @CheckForNull
  172. public String getOrganizationUrl() {
  173. return organizationUrl;
  174. }
  175. @CheckForNull
  176. public String getLicense() {
  177. return license;
  178. }
  179. @CheckForNull
  180. public String getHomepageUrl() {
  181. return homepageUrl;
  182. }
  183. @CheckForNull
  184. public String getIssueTrackerUrl() {
  185. return issueTrackerUrl;
  186. }
  187. public boolean isUseChildFirstClassLoader() {
  188. return useChildFirstClassLoader;
  189. }
  190. public boolean isSonarLintSupported() {
  191. return sonarLintSupported;
  192. }
  193. public String getDocumentationPath() {
  194. return documentationPath;
  195. }
  196. @CheckForNull
  197. public String getBasePlugin() {
  198. return basePlugin;
  199. }
  200. @CheckForNull
  201. public String getImplementationBuild() {
  202. return implementationBuild;
  203. }
  204. public Set<RequiredPlugin> getRequiredPlugins() {
  205. return requiredPlugins;
  206. }
  207. public PluginInfo setName(@Nullable String name) {
  208. this.name = (name != null ? name : this.key);
  209. return this;
  210. }
  211. public PluginInfo setVersion(Version version) {
  212. this.version = version;
  213. return this;
  214. }
  215. public PluginInfo setMinimalSqVersion(@Nullable Version v) {
  216. this.minimalSqVersion = v;
  217. return this;
  218. }
  219. public PluginInfo setDocumentationPath(@Nullable String documentationPath) {
  220. this.documentationPath = documentationPath;
  221. return this;
  222. }
  223. /**
  224. * Required
  225. */
  226. public PluginInfo setMainClass(String mainClass) {
  227. this.mainClass = mainClass;
  228. return this;
  229. }
  230. public PluginInfo setDescription(@Nullable String description) {
  231. this.description = description;
  232. return this;
  233. }
  234. public PluginInfo setOrganizationName(@Nullable String s) {
  235. this.organizationName = s;
  236. return this;
  237. }
  238. public PluginInfo setOrganizationUrl(@Nullable String s) {
  239. this.organizationUrl = s;
  240. return this;
  241. }
  242. public PluginInfo setLicense(@Nullable String license) {
  243. this.license = license;
  244. return this;
  245. }
  246. public PluginInfo setHomepageUrl(@Nullable String s) {
  247. this.homepageUrl = s;
  248. return this;
  249. }
  250. public PluginInfo setIssueTrackerUrl(@Nullable String s) {
  251. this.issueTrackerUrl = s;
  252. return this;
  253. }
  254. public PluginInfo setUseChildFirstClassLoader(boolean b) {
  255. this.useChildFirstClassLoader = b;
  256. return this;
  257. }
  258. public PluginInfo setSonarLintSupported(boolean sonarLintPlugin) {
  259. this.sonarLintSupported = sonarLintPlugin;
  260. return this;
  261. }
  262. public PluginInfo setBasePlugin(@Nullable String s) {
  263. if ("l10nen".equals(s)) {
  264. LOGGER.info("Plugin [{}] defines 'l10nen' as base plugin. " +
  265. "This metadata can be removed from manifest of l10n plugins since version 5.2.", key);
  266. basePlugin = null;
  267. } else {
  268. basePlugin = s;
  269. }
  270. return this;
  271. }
  272. public PluginInfo setImplementationBuild(@Nullable String implementationBuild) {
  273. this.implementationBuild = implementationBuild;
  274. return this;
  275. }
  276. public PluginInfo addRequiredPlugin(RequiredPlugin p) {
  277. this.requiredPlugins.add(p);
  278. return this;
  279. }
  280. /**
  281. * Find out if this plugin is compatible with a given version of SonarQube.
  282. * The version of SQ must be greater than or equal to the minimal version
  283. * needed by the plugin.
  284. */
  285. public boolean isCompatibleWith(String runtimeVersion) {
  286. if (null == this.minimalSqVersion) {
  287. // no constraint defined on the plugin
  288. return true;
  289. }
  290. Version effectiveMin = Version.create(minimalSqVersion.getName()).removeQualifier();
  291. Version effectiveVersion = Version.create(runtimeVersion).removeQualifier();
  292. if (runtimeVersion.endsWith("-SNAPSHOT")) {
  293. // check only the major and minor versions (two first fields)
  294. effectiveMin = Version.create(effectiveMin.getMajor() + "." + effectiveMin.getMinor());
  295. }
  296. return effectiveVersion.compareTo(effectiveMin) >= 0;
  297. }
  298. @Override
  299. public String toString() {
  300. return String.format("[%s]", SLASH_JOINER.join(key, version, implementationBuild));
  301. }
  302. @Override
  303. public boolean equals(@Nullable Object o) {
  304. if (this == o) {
  305. return true;
  306. }
  307. if (o == null || getClass() != o.getClass()) {
  308. return false;
  309. }
  310. PluginInfo info = (PluginInfo) o;
  311. if (!key.equals(info.key)) {
  312. return false;
  313. }
  314. return !(version != null ? !version.equals(info.version) : info.version != null);
  315. }
  316. @Override
  317. public int hashCode() {
  318. int result = key.hashCode();
  319. result = 31 * result + (version != null ? version.hashCode() : 0);
  320. return result;
  321. }
  322. @Override
  323. public int compareTo(PluginInfo that) {
  324. return ComparisonChain.start()
  325. .compare(this.name, that.name)
  326. .compare(this.version, that.version, Ordering.natural().nullsFirst())
  327. .result();
  328. }
  329. public static PluginInfo create(File jarFile) {
  330. try {
  331. PluginManifest manifest = new PluginManifest(jarFile);
  332. return create(jarFile, manifest);
  333. } catch (IOException e) {
  334. throw new IllegalStateException("Fail to extract plugin metadata from file: " + jarFile, e);
  335. }
  336. }
  337. @VisibleForTesting
  338. static PluginInfo create(File jarFile, PluginManifest manifest) {
  339. if (StringUtils.isBlank(manifest.getKey())) {
  340. throw MessageException.of(String.format("File is not a plugin. Please delete it and restart: %s", jarFile.getAbsolutePath()));
  341. }
  342. PluginInfo info = new PluginInfo(manifest.getKey());
  343. info.setJarFile(jarFile);
  344. info.setName(manifest.getName());
  345. info.setMainClass(manifest.getMainClass());
  346. info.setVersion(Version.create(manifest.getVersion()));
  347. info.setDocumentationPath(getDocumentationPath(jarFile));
  348. // optional fields
  349. info.setDescription(manifest.getDescription());
  350. info.setLicense(manifest.getLicense());
  351. info.setOrganizationName(manifest.getOrganization());
  352. info.setOrganizationUrl(manifest.getOrganizationUrl());
  353. info.setDisplayVersion(manifest.getDisplayVersion());
  354. String minSqVersion = manifest.getSonarVersion();
  355. if (minSqVersion != null) {
  356. info.setMinimalSqVersion(Version.create(minSqVersion));
  357. }
  358. info.setHomepageUrl(manifest.getHomepage());
  359. info.setIssueTrackerUrl(manifest.getIssueTrackerUrl());
  360. info.setUseChildFirstClassLoader(manifest.isUseChildFirstClassLoader());
  361. info.setSonarLintSupported(manifest.isSonarLintSupported());
  362. info.setBasePlugin(manifest.getBasePlugin());
  363. info.setImplementationBuild(manifest.getImplementationBuild());
  364. String[] requiredPlugins = manifest.getRequirePlugins();
  365. if (requiredPlugins != null) {
  366. Arrays.stream(requiredPlugins)
  367. .map(RequiredPlugin::parse)
  368. .filter(t -> !"license".equals(t.key))
  369. .forEach(info::addRequiredPlugin);
  370. }
  371. return info;
  372. }
  373. private static String getDocumentationPath(File file) {
  374. try (JarFile jarFile = new JarFile(file)) {
  375. return Optional.ofNullable(jarFile.getEntry("static/documentation.md"))
  376. .map(ZipEntry::getName)
  377. .orElse(null);
  378. } catch (IOException e) {
  379. LOGGER.warn("Could not retrieve documentation path from " + file, e);
  380. }
  381. return null;
  382. }
  383. }