/** * This file is part of Graylog. * * Graylog is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Graylog 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Graylog. If not, see <http://www.gnu.org/licenses/>. */ package org.graylog2.plugin; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; import java.net.URL; import java.util.Properties; import static com.google.common.base.Strings.isNullOrEmpty; import static java.util.Objects.requireNonNull; /** * Following the <a href="http://semver.org/">Semantic Versioning specification</a>. */ public class Version implements Comparable<Version> { private static final Logger LOG = LoggerFactory.getLogger(Version.class); /** * @deprecated Use {@link #getVersion()} */ @Deprecated public final int major; /** * @deprecated Use {@link #getVersion()} */ @Deprecated public final int minor; /** * @deprecated Use {@link #getVersion()} */ @Deprecated public final int patch; /** * @deprecated Use {@link #getVersion()} */ @Deprecated public final String additional; /** * @deprecated Use {@link #getVersion()} */ @Deprecated public final String abbrevCommitSha; private final com.github.zafarkhaja.semver.Version version; /** * Reads the current version from the classpath, using version.properties and git.properties. */ public static final Version CURRENT_CLASSPATH = fromClasspathProperties(Version.from(0, 0, 0, "unknown")); /** * @deprecated Use {@link #Version(com.github.zafarkhaja.semver.Version)} or {@link #from(int, int, int)} */ @Deprecated public Version(int major, int minor, int patch) { this(buildSemVer(major, minor, patch, null, null)); } /** * @deprecated Use {@link #Version(com.github.zafarkhaja.semver.Version)} or {@link #from(int, int, int, String)} */ @Deprecated public Version(int major, int minor, int patch, String additional) { this(buildSemVer(major, minor, patch, additional, null)); } /** * @deprecated Use {@link #Version(com.github.zafarkhaja.semver.Version)} */ @Deprecated public Version(int major, int minor, int patch, String additional, String abbrevCommitSha) { this(buildSemVer(major, minor, patch, additional, abbrevCommitSha)); } public Version(com.github.zafarkhaja.semver.Version version) { this.version = requireNonNull(version); // Deprecated this.major = version.getMajorVersion(); this.minor = version.getMinorVersion(); this.patch = version.getPatchVersion(); this.additional = version.getPreReleaseVersion(); this.abbrevCommitSha = version.getBuildMetadata(); } /** * Build valid {@link Version} from major, minor, and patch version ("X.Y.Z"). * * @param major The major version component. * @param minor The minor version component. * @param patch The patch version component. * @return The {@link Version} instance built from the given parameters. */ public static Version from(int major, int minor, int patch) { return new Version(com.github.zafarkhaja.semver.Version.forIntegers(major, minor, patch)); } /** * Build valid {@link Version} from major, minor, and patch version ("X.Y.Z"). * * @param major The major version component. * @param minor The minor version component. * @param patch The patch version component. * @param preRelease The pre-release version component. * @return The {@link Version} instance built from the given parameters. */ public static Version from(int major, int minor, int patch, String preRelease) { return new Version(buildSemVer(major, minor, patch, preRelease, null)); } /** * Build valid {@link Version} from major, minor, and patch version ("X.Y.Z"). * * @param major The major version component. * @param minor The minor version component. * @param patch The patch version component. * @param preRelease The pre-release version component. * @param buildMetadata Additional build metadata (e. g. the Git commit SHA). * @return The {@link Version} instance built from the given parameters. */ public static Version from(int major, int minor, int patch, String preRelease, String buildMetadata) { return new Version(buildSemVer(major, minor, patch, preRelease, buildMetadata)); } private static com.github.zafarkhaja.semver.Version buildSemVer(int major, int minor, int patch, String preRelease, String buildMetadata) { com.github.zafarkhaja.semver.Version version = com.github.zafarkhaja.semver.Version.forIntegers(major, minor, patch); if (!isNullOrEmpty(preRelease)) { version = version.setPreReleaseVersion(preRelease); } if (!isNullOrEmpty(buildMetadata)) { version = version.setBuildMetadata(buildMetadata); } return version; } /** * Try to read the version from the {@literal graylog-plugin.properties} file included in a plugin. * * @param pluginClass Class where the class loader should be obtained from. * @param path Path of the properties file on the classpath which contains the version information. * @param propertyName The name of the property to read as project version ("major.minor.patch-preReleaseVersion"). * @param defaultVersion The {@link Version} to return if reading the information from the properties files failed. */ public static Version fromPluginProperties(Class<?> pluginClass, String path, String propertyName, Version defaultVersion) { return fromClasspathProperties(pluginClass, path, propertyName, null, null, defaultVersion); } /** * Try to read the version from {@literal version.properties} ({@code project.version} property) * and {@literal git.properties} ({@code git.commit.id} property) from the classpath. * * @param defaultVersion The {@link Version} to return if reading the information from the properties files failed. */ public static Version fromClasspathProperties(Version defaultVersion) { return fromClasspathProperties("version.properties", "project.version", "git.properties", "git.commit.id", defaultVersion); } /** * Try to read the version from {@code path} ({@code project.version} property) * and {@code gitPath} ({@code git.commit.id} property) from the classpath. * * @param path Path of the properties file on the classpath which contains the version information. * @param gitPath Path of the properties file on the classpath which contains the SCM information. * @param defaultVersion The {@link Version} to return if reading the information from the properties files failed. */ public static Version fromClasspathProperties(String path, String gitPath, Version defaultVersion) { return fromClasspathProperties(path, "project.version", gitPath, "git.commit.id", defaultVersion); } /** * Try to read the version from {@code path} ({@code propertyName} property) * and {@code gitPath} ({@code gitPropertyName} property) from the classpath. * * @param path Path of the properties file on the classpath which contains the version information. * @param propertyName The name of the property to read as project version ("major.minor.patch-preReleaseVersion"). * @param gitPath Path of the properties file on the classpath which contains the SCM information. * @param gitPropertyName The name of the property to read as git commit SHA. * @param defaultVersion The {@link Version} to return if reading the information from the properties files failed. */ public static Version fromClasspathProperties(String path, String propertyName, String gitPath, String gitPropertyName, Version defaultVersion) { return fromClasspathProperties(Version.class, path, propertyName, gitPath, gitPropertyName, defaultVersion); } /** * Try to read the version from {@code path} ({@code propertyName} property) * and {@code gitPath} ({@code gitPropertyName} property) from the classpath. * * @param clazz Class where the class loader should be obtained from. * @param path Path of the properties file on the classpath which contains the version information. * @param propertyName The name of the property to read as project version ("major.minor.patch-preReleaseVersion"). * @param gitPath Path of the properties file on the classpath which contains the SCM information. * @param gitPropertyName The name of the property to read as git commit SHA. * @param defaultVersion The {@link Version} to return if reading the information from the properties files failed. */ public static Version fromClasspathProperties(@Nonnull Class<?> clazz, String path, String propertyName, String gitPath, String gitPropertyName, Version defaultVersion) { try { final URL resource = getResource(clazz, path); final Properties versionProperties = new Properties(); versionProperties.load(resource.openStream()); final com.github.zafarkhaja.semver.Version version = com.github.zafarkhaja.semver.Version.valueOf(versionProperties.getProperty(propertyName)); final int major = version.getMajorVersion(); final int minor = version.getMinorVersion(); final int patch = version.getPatchVersion(); final String qualifier = version.getPreReleaseVersion(); String commitSha = null; try { final Properties git = new Properties(); final URL gitResource = getResource(clazz, gitPath); git.load(gitResource.openStream()); commitSha = git.getProperty(gitPropertyName); // abbreviate if present and looks like a long sha if (commitSha != null && commitSha.length() > 7) { commitSha = commitSha.substring(0, 7); } } catch (Exception e) { LOG.debug("Git commit details are not available, skipping.", e); } return from(major, minor, patch, qualifier, commitSha); } catch (Exception e) { LOG.error("Unable to read " + path + ", this build has no version number.", e); } return defaultVersion; } private static URL getResource(Class<?> clazz, String path) { final URL url = requireNonNull(clazz, "Class argument is null!").getClassLoader().getResource(path); return requireNonNull(url, "Resource <" + path + "> not found."); } /** * @return the underlying {@link com.github.zafarkhaja.semver.Version} instance. */ public com.github.zafarkhaja.semver.Version getVersion() { return version; } /** * {@inheritDoc} */ @Override public String toString() { return version.toString(); } /** * Check if this version is higher than the passed other version. Only taking major and minor version number in account. * * @param other {@link Version} to compare */ @Deprecated public boolean greaterMinor(Version other) { return other.major < this.major || other.major == this.major && other.minor < this.minor; } /** * @see com.github.zafarkhaja.semver.Version#greaterThanOrEqualTo(com.github.zafarkhaja.semver.Version) */ public boolean sameOrHigher(Version other) { if (isNullOrEmpty(version.getPreReleaseVersion())) { return version.greaterThanOrEqualTo(other.getVersion()); } else { // If this is a pre-release version, use the major.minor.patch version for comparison with the other. // This allows plugins to require a server version of 2.1.0 and it still gets loaded on a 2.1.0-beta.2 server. // See: https://github.com/Graylog2/graylog2-server/issues/2462 return com.github.zafarkhaja.semver.Version.valueOf(version.getNormalVersion()).greaterThanOrEqualTo(other.getVersion()); } } /** * {@inheritDoc} */ @Override public boolean equals(final Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final Version that = (Version) o; return version.equals(that.getVersion()); } /** * {@inheritDoc} */ @Override public int hashCode() { return version.hashCode(); } /** * {@inheritDoc} */ @Override public int compareTo(@Nonnull Version that) { requireNonNull(that); return version.compareTo(that.getVersion()); } }