/* * SonarQube * Copyright (C) 2009-2017 SonarSource SA * mailto:info AT sonarsource DOT com * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.sonar.core.platform; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; import com.google.common.collect.ComparisonChain; import com.google.common.collect.Ordering; import java.io.File; import java.io.IOException; import java.util.HashSet; import java.util.Set; import java.util.regex.Pattern; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.apache.commons.lang.StringUtils; import org.sonar.api.utils.MessageException; import org.sonar.api.utils.log.Loggers; import org.sonar.updatecenter.common.PluginManifest; import org.sonar.updatecenter.common.Version; public class PluginInfo implements Comparable<PluginInfo> { private static final Joiner SLASH_JOINER = Joiner.on(" / ").skipNulls(); public static class RequiredPlugin { private static final Pattern PARSER = Pattern.compile("\\w+:.+"); private final String key; private final Version minimalVersion; public RequiredPlugin(String key, Version minimalVersion) { this.key = key; this.minimalVersion = minimalVersion; } public String getKey() { return key; } public Version getMinimalVersion() { return minimalVersion; } public static RequiredPlugin parse(String s) { if (!PARSER.matcher(s).matches()) { throw new IllegalArgumentException("Manifest field does not have correct format: " + s); } String[] fields = StringUtils.split(s, ':'); return new RequiredPlugin(fields[0], Version.create(fields[1]).removeQualifier()); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } RequiredPlugin that = (RequiredPlugin) o; return key.equals(that.key); } @Override public int hashCode() { return key.hashCode(); } @Override public String toString() { return new StringBuilder().append(key).append(':').append(minimalVersion.getName()).toString(); } } private final String key; private String name; @CheckForNull private File jarFile; @CheckForNull private String mainClass; @CheckForNull private Version version; private String displayVersion; @CheckForNull private Version minimalSqVersion; @CheckForNull private String description; @CheckForNull private String organizationName; @CheckForNull private String organizationUrl; @CheckForNull private String license; @CheckForNull private String homepageUrl; @CheckForNull private String issueTrackerUrl; private boolean useChildFirstClassLoader; @CheckForNull private String basePlugin; @CheckForNull private String implementationBuild; @CheckForNull private boolean sonarLintSupported; private final Set<RequiredPlugin> requiredPlugins = new HashSet<>(); public PluginInfo(String key) { Preconditions.checkNotNull(key, "Plugin key is missing from manifest"); this.key = key; this.name = key; } public PluginInfo setJarFile(@Nullable File f) { this.jarFile = f; return this; } @CheckForNull public File getJarFile() { return jarFile; } public File getNonNullJarFile() { Preconditions.checkNotNull(jarFile); return jarFile; } public String getKey() { return key; } public String getName() { return name; } @CheckForNull public Version getVersion() { return version; } @CheckForNull public String getDisplayVersion() { return displayVersion; } public PluginInfo setDisplayVersion(@Nullable String displayVersion) { this.displayVersion = displayVersion; return this; } @CheckForNull public Version getMinimalSqVersion() { return minimalSqVersion; } @CheckForNull public String getMainClass() { return mainClass; } @CheckForNull public String getDescription() { return description; } @CheckForNull public String getOrganizationName() { return organizationName; } @CheckForNull public String getOrganizationUrl() { return organizationUrl; } @CheckForNull public String getLicense() { return license; } @CheckForNull public String getHomepageUrl() { return homepageUrl; } @CheckForNull public String getIssueTrackerUrl() { return issueTrackerUrl; } public boolean isUseChildFirstClassLoader() { return useChildFirstClassLoader; } @CheckForNull public boolean isSonarLintSupported() { return sonarLintSupported; } @CheckForNull public String getBasePlugin() { return basePlugin; } @CheckForNull public String getImplementationBuild() { return implementationBuild; } public Set<RequiredPlugin> getRequiredPlugins() { return requiredPlugins; } public PluginInfo setName(@Nullable String name) { this.name = MoreObjects.firstNonNull(name, this.key); return this; } public PluginInfo setVersion(Version version) { this.version = version; return this; } public PluginInfo setMinimalSqVersion(@Nullable Version v) { this.minimalSqVersion = v; return this; } /** * Required */ public PluginInfo setMainClass(String mainClass) { this.mainClass = mainClass; return this; } public PluginInfo setDescription(@Nullable String description) { this.description = description; return this; } public PluginInfo setOrganizationName(@Nullable String s) { this.organizationName = s; return this; } public PluginInfo setOrganizationUrl(@Nullable String s) { this.organizationUrl = s; return this; } public PluginInfo setLicense(@Nullable String license) { this.license = license; return this; } public PluginInfo setHomepageUrl(@Nullable String s) { this.homepageUrl = s; return this; } public PluginInfo setIssueTrackerUrl(@Nullable String s) { this.issueTrackerUrl = s; return this; } public PluginInfo setUseChildFirstClassLoader(boolean b) { this.useChildFirstClassLoader = b; return this; } public PluginInfo setSonarLintSupported(boolean sonarLintPlugin) { this.sonarLintSupported = sonarLintPlugin; return this; } public PluginInfo setBasePlugin(@Nullable String s) { if ("l10nen".equals(s)) { Loggers.get(PluginInfo.class).info("Plugin [{}] defines 'l10nen' as base plugin. " + "This metadata can be removed from manifest of l10n plugins since version 5.2.", key); basePlugin = null; } else { basePlugin = s; } return this; } public PluginInfo setImplementationBuild(@Nullable String implementationBuild) { this.implementationBuild = implementationBuild; return this; } public PluginInfo addRequiredPlugin(RequiredPlugin p) { this.requiredPlugins.add(p); return this; } /** * Find out if this plugin is compatible with a given version of SonarQube. * The version of SQ must be greater than or equal to the minimal version * needed by the plugin. */ public boolean isCompatibleWith(String runtimeVersion) { if (null == this.minimalSqVersion) { // no constraint defined on the plugin return true; } Version effectiveMin = Version.create(minimalSqVersion.getName()).removeQualifier(); Version effectiveVersion = Version.create(runtimeVersion).removeQualifier(); if (runtimeVersion.endsWith("-SNAPSHOT")) { // check only the major and minor versions (two first fields) effectiveMin = Version.create(effectiveMin.getMajor() + "." + effectiveMin.getMinor()); } return effectiveVersion.compareTo(effectiveMin) >= 0; } @Override public String toString() { return String.format("[%s]", SLASH_JOINER.join(key, version, implementationBuild)); } @Override public boolean equals(@Nullable Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } PluginInfo info = (PluginInfo) o; if (!key.equals(info.key)) { return false; } return !(version != null ? !version.equals(info.version) : info.version != null); } @Override public int hashCode() { int result = key.hashCode(); result = 31 * result + (version != null ? version.hashCode() : 0); return result; } @Override public int compareTo(PluginInfo that) { return ComparisonChain.start() .compare(this.name, that.name) .compare(this.version, that.version, Ordering.natural().nullsFirst()) .result(); } public static PluginInfo create(File jarFile) { try { PluginManifest manifest = new PluginManifest(jarFile); return create(jarFile, manifest); } catch (IOException e) { throw new IllegalStateException("Fail to extract plugin metadata from file: " + jarFile, e); } } @VisibleForTesting static PluginInfo create(File jarFile, PluginManifest manifest) { if (StringUtils.isBlank(manifest.getKey())) { throw MessageException.of(String.format("File is not a plugin. Please delete it and restart: %s", jarFile.getAbsolutePath())); } PluginInfo info = new PluginInfo(manifest.getKey()); info.setJarFile(jarFile); info.setName(manifest.getName()); info.setMainClass(manifest.getMainClass()); info.setVersion(Version.create(manifest.getVersion())); // optional fields info.setDescription(manifest.getDescription()); info.setLicense(manifest.getLicense()); info.setOrganizationName(manifest.getOrganization()); info.setOrganizationUrl(manifest.getOrganizationUrl()); info.setDisplayVersion(manifest.getDisplayVersion()); String minSqVersion = manifest.getSonarVersion(); if (minSqVersion != null) { info.setMinimalSqVersion(Version.create(minSqVersion)); } info.setHomepageUrl(manifest.getHomepage()); info.setIssueTrackerUrl(manifest.getIssueTrackerUrl()); info.setUseChildFirstClassLoader(manifest.isUseChildFirstClassLoader()); info.setSonarLintSupported(manifest.isSonarLintSupported()); info.setBasePlugin(manifest.getBasePlugin()); info.setImplementationBuild(manifest.getImplementationBuild()); String[] requiredPlugins = manifest.getRequirePlugins(); if (requiredPlugins != null) { for (String s : requiredPlugins) { info.addRequiredPlugin(RequiredPlugin.parse(s)); } } return info; } private enum JarToPluginInfo implements Function<File, PluginInfo> { INSTANCE; @Override public PluginInfo apply(@Nonnull File jarFile) { return create(jarFile); } } public static Function<File, PluginInfo> jarToPluginInfo() { return JarToPluginInfo.INSTANCE; } }