net.snowflake.common.core.ComponentInfo Maven / Gradle / Ivy
/*
* Copyright (c) 2013 Snowflake Computing Inc. All right reserved.
*/
package net.snowflake.common.core;
import com.fasterxml.jackson.annotation.JsonIgnore;
import java.io.InputStream;
import java.net.URI;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.jar.Attributes;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;
/**
* Component information, like version, SVN rev, etc is available through this class.
*
* It works by interrogating the class passed to the initialize() method. Current implementation
* retrieves the version information from the jar that the passed in class had been loaded from, if
* any.
*
*
*
* @author ppovinec
*/
public class ComponentInfo {
/** Supported version format pattern */
public static final String VERSION_FORMAT_PATTERN =
"[0-9]+(\\.[0-9]+)(\\.[0-9]+)(\\.[0-9a-zA-Z_-]+)?";
/** The delimiter between the numbers in the version number */
private static final String VERSION_DELIMITER = "\\.";
/**
* Version info - purely a data class. All fields set to -1 if the value could not be determined.
*/
public static class Version implements Comparable {
private final int majorVersion;
private final int minorVersion;
private final int patchVersion;
private String buildId;
public Version() {
this.majorVersion = 0;
this.minorVersion = 0;
this.patchVersion = 0;
this.buildId = null;
}
public Version(int majorVersion, int minorVersion, int patchVersion, String buildId) {
this.majorVersion = majorVersion;
this.minorVersion = minorVersion;
this.patchVersion = patchVersion;
this.buildId = buildId;
}
public Version(String version) {
// only initialize if version has correct format
if (version != null && version.matches(VERSION_FORMAT_PATTERN)) {
List versionDecomposed = Arrays.asList(version.split(VERSION_DELIMITER, -1));
if (versionDecomposed.size() >= 3) {
this.majorVersion = Integer.parseInt(versionDecomposed.get(0));
this.minorVersion = Integer.parseInt(versionDecomposed.get(1));
this.patchVersion = Integer.parseInt(versionDecomposed.get(2));
return;
}
}
this.majorVersion = 0;
this.minorVersion = 0;
this.patchVersion = 0;
}
/**
* Reverse engineer a Version from a version-rank. WARNING: Keep this consistent with
* getVersionRank
*
* @param versionRank - the version rank
* @return - the corresponding version
*/
public static final Version fromRank(long versionRank) {
return new Version(
(int) (versionRank >> 32), // majorVersion
(int) ((versionRank >> 16) & Short.MAX_VALUE), // minorVersion
(int) (versionRank & Short.MAX_VALUE), // patchVersion
"" // buildId
);
}
/**
* Generate an absolute version ranking allowing to compare two versions
*
* @return version rank
*/
@JsonIgnore
public long getVersionRank() {
return (((long) this.majorVersion) << 32)
| (((long) this.minorVersion) << 16)
| ((long) this.patchVersion);
}
public int getMajorVersion() {
return majorVersion;
}
public int getMinorVersion() {
return minorVersion;
}
public int getPatchVersion() {
return patchVersion;
}
public String getBuildId() {
return buildId;
}
/**
* Backward compatibility, extract svn revision from build id
*
* @return SVN revision
*/
@JsonIgnore
public int getSvnRevisionObsolete() {
try {
return (buildId != null) ? Integer.parseInt(this.buildId) : -1;
} catch (NumberFormatException ex) {
return 0;
}
}
/**
* Return the GS version number as a string so that it can be displayed by the UI
*
* @param showBuildNumber true if we should add the build number (internal)
* @return version of that GS instance as a string
*/
public String getVersionAsString(boolean showBuildNumber) {
return getVersionAsString(showBuildNumber, true);
}
public String getVersionAsString(boolean showBuildNumber, boolean includePatchVersion) {
String versionStr;
if (majorVersion == -1) {
versionStr = "Dev";
} else {
versionStr =
((majorVersion > -1) ? majorVersion : 0)
+ "."
+ ((minorVersion > -1) ? minorVersion : 0);
if (includePatchVersion) {
versionStr += "." + ((patchVersion > -1) ? patchVersion : 0);
}
if (majorVersion == 0) {
versionStr += " (Beta)";
}
}
if (showBuildNumber && buildId != null) {
versionStr += " b" + buildId;
}
return versionStr;
}
/**
* This is to be able to de-serialize old export files
*
* @param svnRevision svn revision number
*/
public void setSvnRevision(int svnRevision) {
this.buildId = String.valueOf(svnRevision);
}
@Override
public int compareTo(Version other) {
if (this == other) {
return 0;
}
return Comparator.comparingInt(Version::getMajorVersion)
.thenComparingInt(Version::getMinorVersion)
.thenComparingInt(Version::getPatchVersion)
// (spelley)
// this isn't a true numeric sort but whatever. We're moving to
// git buildIds anyways. So long as 2 String-but-numeric buildIds
// have the same number of chars/digits this will correctly
// compare them. I did this just to be able to test GS job retry
// on a test deployment
.thenComparing(Version::getBuildId, Comparator.nullsLast(Comparator.naturalOrder()))
.compare(this, other);
}
@Override
public String toString() {
return "majorVersion="
+ majorVersion
+ ", minorVersion="
+ minorVersion
+ ", patchVersion="
+ patchVersion
+ ", buildId="
+ buildId;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Version)) {
return false;
}
final Version version = (Version) o;
return majorVersion == version.majorVersion
&& minorVersion == version.minorVersion
&& patchVersion == version.patchVersion
&& Objects.equals(buildId, version.buildId);
}
@Override
public int hashCode() {
return Objects.hash(majorVersion, minorVersion, patchVersion, buildId);
}
}
private static Version theVersion;
private static boolean initialized = false;
/**
* Get the version info part of the ComponentInfo.
*
*
*
* @return version info
* @throws IllegalStateException if the ComponentInfo has not been initialized
*/
public static Version getVersion() {
if (!initialized) {
throw new IllegalStateException("ComponentInfo not initialized");
}
return theVersion;
}
/**
* Initialize the ComponentInfo by retrieving all the necessary information from the available
* sources.
*
*
*
* @throws Exception if unable to complete the initialization
*/
public static void initialize() throws Exception {
initialize(net.snowflake.common.core.ComponentInfo.class);
}
/**
* Initialize the ComponentInfo by retrieving all the necessary information from the available
* sources.
*
*
*
* @param klass that should be interrogated
* @throws Exception if unable to complete the initialization
*/
public static void initialize(Class> klass) throws Exception {
String implVersion;
String buildId;
// Get the URI of the source of the passed in class.
URI jarURI = klass.getProtectionDomain().getCodeSource().getLocation().toURI();
// If this class is not loaded from a jar, assume we are in dev, and all
// versions are returned as -1.
if (!jarURI.toString().endsWith(".jar")) {
theVersion = new Version(-1, -1, -1, null);
initialized = true;
return;
}
// Open the jar as a stream and read its manifest.
InputStream is = null;
try {
is = jarURI.toURL().openStream();
if (is == null) {
throw new RuntimeException("Couldn't open jar:" + jarURI);
}
JarInputStream jarStream = new JarInputStream(is);
Manifest mf = jarStream.getManifest();
if (mf == null) {
throw new IllegalStateException("Couldn't find manifest in:" + jarURI.toURL());
}
// Parse the manifest to retrieve implementation version and SVN version.
Attributes mainAttribs = mf.getMainAttributes();
implVersion = mainAttribs.getValue("Snowflake-Version");
buildId = mainAttribs.getValue("Snowflake-BuildId");
} catch (Exception ex) {
// Simply rethrow...
throw ex;
} finally {
if (is != null) {
is.close();
}
}
// Parse implementation version as major.minor. Anything that doesn't
// conform is silently translated to -1.-1 (this is mostly to workaround
// the version strings like 1.0-SNAPSHOT in dev.
int major = -1;
int minor = -1;
int patch = -1;
if (implVersion != null) {
try {
String[] majorMinorPatch = implVersion.split("\\.");
if (majorMinorPatch != null && majorMinorPatch.length >= 2) {
major = Integer.parseInt(majorMinorPatch[0]);
minor = Integer.parseInt(majorMinorPatch[1]);
if (majorMinorPatch.length >= 3) patch = Integer.parseInt(majorMinorPatch[2]);
else patch = 0;
}
} catch (Exception ex) {
// Swallow. Will use default of -1.
}
}
// Only Version in ComponentInfo now, so we're done with the initialization.
theVersion = new Version(major, minor, patch, buildId);
initialized = true;
}
/**
* Initialize the ComponentInfo by explicitly passing it a {@link Version}.
*
* @param version that should be stored
*/
public static void initialize(final Version version) {
theVersion = version;
initialized = version != null;
}
}