From 3536dded1bb7714056cd78e152882e33d5baa049 Mon Sep 17 00:00:00 2001
From: Rick Herrick <jrherrick@wustl.edu>
Date: Wed, 11 May 2016 23:49:19 -0500
Subject: [PATCH] Removed old version REST API, added new buildInfo call in
 siteConfig, added build plugin to retrieve git repo info and build number.

---
 build.gradle                                  | 28 ++++++++++-
 .../nrg/xapi/rest/settings/SiteConfigApi.java | 35 ++++++++++++++
 .../nrg/xnat/initialization/RootConfig.java   | 11 +++++
 .../org/nrg/xnat/restlet/XNATApplication.java |  2 -
 .../resources/VersionRepresentation.java      | 46 -------------------
 .../webapp/WEB-INF/conf/xnat-security.xml     |  2 -
 src/main/webapp/scripts/footer.js             | 10 ++--
 src/main/webapp/scripts/timeLeft.js           |  2 +-
 8 files changed, 80 insertions(+), 56 deletions(-)
 delete mode 100644 src/main/java/org/nrg/xnat/restlet/resources/VersionRepresentation.java

diff --git a/build.gradle b/build.gradle
index c4358a0c..7822995b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -18,11 +18,14 @@ def vGroovy = '2.4.6'
 def vJython = '2.7.0'
 
 group 'org.nrg.xnat'
-version vXnat
 
 buildscript {
     repositories {
         mavenLocal()
+        jcenter()
+        maven {
+            url "https://plugins.gradle.org/m2/"
+        }
         maven {
             url 'https://nrgxnat.artifactoryonline.com/nrgxnat/libs-release'
             name 'XNAT Release Repository'
@@ -31,11 +34,11 @@ buildscript {
             url 'https://nrgxnat.artifactoryonline.com/nrgxnat/libs-snapshot'
             name 'XNAT Snapshot Repository'
         }
-        jcenter()
     }
     dependencies {
         classpath "com.bmuschko:gradle-cargo-plugin:2.2.2"
         classpath "com.bmuschko:gradle-tomcat-plugin:2.2.4"
+        classpath "gradle.plugin.com.zoltu.gradle.plugin:git-versioning:2.0.19"
     }
 }
 
@@ -46,6 +49,7 @@ apply plugin: 'maven'
 apply plugin: 'maven-publish'
 apply plugin: 'com.bmuschko.tomcat'
 apply plugin: 'com.bmuschko.cargo'
+apply plugin: "com.zoltu.git-versioning"
 apply plugin: 'idea'
 apply plugin: 'eclipse'
 
@@ -135,6 +139,26 @@ if (JavaVersion.current().isJava8Compatible()) {
     }
 }
 
+// Pulls in the Jenkins BUILD_NUMBER environment variable if available.
+def buildNumber = hasProperty("BUILD_NUMBER") ? getProperty("BUILD_NUMBER") : "Manual"
+def buildDate = new Date()
+
+jar {
+    manifest {
+        attributes 'Build-Number': buildNumber,
+                'Build-Date': buildDate,
+                'Application-Name': 'XNAT'
+    }
+}
+
+war {
+    manifest {
+        attributes 'Build-Number': buildNumber,
+                'Build-Date': buildDate,
+                'Application-Name': 'XNAT'
+    }
+}
+
 task sourceJar(type: Jar, dependsOn: classes) {
     from sourceSets.main.allSource
 }
diff --git a/src/main/java/org/nrg/xapi/rest/settings/SiteConfigApi.java b/src/main/java/org/nrg/xapi/rest/settings/SiteConfigApi.java
index 6c3ca05a..fcdad8eb 100644
--- a/src/main/java/org/nrg/xapi/rest/settings/SiteConfigApi.java
+++ b/src/main/java/org/nrg/xapi/rest/settings/SiteConfigApi.java
@@ -21,11 +21,40 @@ import org.springframework.web.bind.annotation.RequestMethod;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Properties;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
 
 @Api(description = "Site Configuration Management API")
 @XapiRestController
 @RequestMapping(value = "/siteConfig")
 public class SiteConfigApi extends AbstractXnatRestApi {
+    @ApiOperation(value = "Returns a map of application build properties.", notes = "This includes the implementation version, Git commit hash, and build number and number.", response = Properties.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Application build properties successfully retrieved."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @RequestMapping(value = "buildInfo", produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET})
+    public ResponseEntity<Properties> getBuildInfo() {
+        if (_log.isDebugEnabled()) {
+            _log.debug("User " + getSessionUser().getUsername() + " requested the application build information.");
+        }
+
+        if (_properties.size() == 0) {
+            final Attributes attributes  = _manifest.getMainAttributes();
+            _properties.setProperty("buildNumber",  attributes.getValue("Build-Number"));
+            _properties.setProperty("buildDate", attributes.getValue("Build-Date"));
+            _properties.setProperty("version", attributes.getValue("Implementation-Version"));
+            _properties.setProperty("commit", attributes.getValue("Implementation-Sha"));
+            if (_log.isDebugEnabled()) {
+                _log.debug("Initialized application build information:\n * Version: {}\n * Build number: {}\n * Build Date: {}\n * Commit: {}",
+                           _properties.getProperty("version"),
+                           _properties.getProperty("buildNumber"),
+                           _properties.getProperty("buildDate"),
+                           _properties.getProperty("commit"));
+            }
+        }
+
+        return new ResponseEntity<>(_properties, HttpStatus.OK);
+    }
+
     @ApiOperation(value = "Returns the full map of site configuration properties.", notes = "Complex objects may be returned as encapsulated JSON strings.", response = SiteConfigPreferences.class)
     @ApiResponses({@ApiResponse(code = 200, message = "Site configuration properties successfully retrieved."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set site configuration properties."), @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET})
@@ -134,4 +163,10 @@ public class SiteConfigApi extends AbstractXnatRestApi {
     @Autowired
     @Lazy
     private SiteConfigPreferences _preferences;
+
+    @Autowired
+    @Lazy
+    private Manifest _manifest;
+
+    private Properties _properties = new Properties();
 }
diff --git a/src/main/java/org/nrg/xnat/initialization/RootConfig.java b/src/main/java/org/nrg/xnat/initialization/RootConfig.java
index 3a539caf..cb878fda 100644
--- a/src/main/java/org/nrg/xnat/initialization/RootConfig.java
+++ b/src/main/java/org/nrg/xnat/initialization/RootConfig.java
@@ -24,9 +24,13 @@ import org.springframework.http.converter.json.MappingJackson2HttpMessageConvert
 import org.springframework.http.converter.xml.MarshallingHttpMessageConverter;
 import org.springframework.oxm.jaxb.Jaxb2Marshaller;
 
+import javax.servlet.ServletContext;
 import javax.xml.bind.Marshaller;
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.jar.Manifest;
 
 /**
  * Configuration for the XNAT root application context. This contains all of the basic infrastructure for initializing
@@ -40,6 +44,13 @@ import java.util.Map;
 @Import({PropertiesConfig.class, DatabaseConfig.class})
 @ImportResource("WEB-INF/conf/xnat-security.xml")
 public class RootConfig {
+    @Bean
+    public Manifest applicationManifest(final ServletContext context) throws IOException {
+        try (final InputStream input = context.getResourceAsStream("/META-INF/MANIFEST.MF")) {
+            return new Manifest(input);
+        }
+    }
+
     @Bean
     public InitializerSiteConfiguration initializerSiteConfiguration() {
         return new InitializerSiteConfiguration();
diff --git a/src/main/java/org/nrg/xnat/restlet/XNATApplication.java b/src/main/java/org/nrg/xnat/restlet/XNATApplication.java
index 1bcbc4b2..d7567871 100755
--- a/src/main/java/org/nrg/xnat/restlet/XNATApplication.java
+++ b/src/main/java/org/nrg/xnat/restlet/XNATApplication.java
@@ -477,8 +477,6 @@ public class XNATApplication extends Application {
     }
 
     private void addPublicRoutes(final Router router, List<Class<? extends Resource>> publicRoutes) {
-        attachURI(router, "/version", VersionRepresentation.class);
-
         if (publicRoutes == null) {
             return;
         }
diff --git a/src/main/java/org/nrg/xnat/restlet/resources/VersionRepresentation.java b/src/main/java/org/nrg/xnat/restlet/resources/VersionRepresentation.java
deleted file mode 100644
index 25355881..00000000
--- a/src/main/java/org/nrg/xnat/restlet/resources/VersionRepresentation.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * org.nrg.xnat.restlet.resources.VersionRepresentation
- * XNAT http://www.xnat.org
- * Copyright (c) 2014, Washington University School of Medicine
- * All Rights Reserved
- *
- * Released under the Simplified BSD.
- *
- * Last modified 7/10/13 9:04 PM
- */
-package org.nrg.xnat.restlet.resources;
-
-import org.nrg.xnat.utils.FileUtils;
-import org.restlet.Context;
-import org.restlet.data.MediaType;
-import org.restlet.data.Request;
-import org.restlet.data.Response;
-import org.restlet.resource.Representation;
-import org.restlet.resource.Resource;
-import org.restlet.resource.StringRepresentation;
-import org.restlet.resource.Variant;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-
-public class VersionRepresentation extends Resource {
-	private final Logger logger = LoggerFactory.getLogger(VersionRepresentation.class);
-
-	public VersionRepresentation(Context context, Request request, Response response) {
-		super(context, request, response);
-		this.getVariants().add(new Variant(MediaType.ALL));
-	}
-
-	@Override
-	public Representation represent(Variant variant) {
-		if (logger.isDebugEnabled()) {
-			logger.debug("Getting XNAT version from the default configuration folder");
-		}
-		try {
-			return new StringRepresentation(FileUtils.getXNATVersion());
-		} catch (IOException exception) {
-			return new StringRepresentation("[Error: check system logs]");
-		}
-	}
-}
diff --git a/src/main/webapp/WEB-INF/conf/xnat-security.xml b/src/main/webapp/WEB-INF/conf/xnat-security.xml
index bd9d4683..1e8e4fee 100644
--- a/src/main/webapp/WEB-INF/conf/xnat-security.xml
+++ b/src/main/webapp/WEB-INF/conf/xnat-security.xml
@@ -111,8 +111,6 @@
                 <value>/app/template/VerificationSent.vm*</value>
                 <value>/app/template/VerifyEmail.vm*</value>
                 <value>/favicon.ico</value>
-                <value>/data/version</value>
-                <value>/REST/version</value>
                 <value>/data/JSESSION</value>
                 <value>/REST/JSESSION</value>
                 <value>/data/services/auth*</value>
diff --git a/src/main/webapp/scripts/footer.js b/src/main/webapp/scripts/footer.js
index 98590d8a..46c53722 100644
--- a/src/main/webapp/scripts/footer.js
+++ b/src/main/webapp/scripts/footer.js
@@ -534,9 +534,13 @@ $(function(){
 
     // add version to title attribute of XNAT logos
     if (typeof logged_in != 'undefined' && logged_in == true){
-        $.get(serverRoot+'/data/version',function(data){
-            XNAT_version = data.split(" ")[0];
-            $('#xnat_power').find('a').attr('title','XNAT version ' + XNAT_version).after('<small>version ' + XNAT_version + '</small>');
+        $.get(serverRoot+'/xapi/siteConfig/buildInfo',function(data){
+            XNAT_version = data.version + " build: " + data.buildNumber;
+            var isNonRelease = /.*(SNAPSHOT|BETA|RC).*/.test(data.version);
+            if (isNonRelease) {
+                XNAT_version += " (" + data.commit + ")";
+            }
+            $('#xnat_power').find('a').attr('title','XNAT version ' + XNAT_version).after('<small>version ' + XNAT_version + (isNonRelease ? "<br>" + data.buildDate : "") + '</small>');
             $('#header_logo').attr('title','XNAT version ' + XNAT_version);
             XNAT.app.version = XNAT_version ;
         });
diff --git a/src/main/webapp/scripts/timeLeft.js b/src/main/webapp/scripts/timeLeft.js
index 4239784a..0572060e 100644
--- a/src/main/webapp/scripts/timeLeft.js
+++ b/src/main/webapp/scripts/timeLeft.js
@@ -180,7 +180,7 @@ if (typeof XNAT.app.timeout == 'undefined'){ XNAT.app.timeout={} }
     timeout.handleOk = function () {
         timeout.hideWarningDialog(timeout.warningDialog);
         timeout.touchCallback.startTime = new Date().getTime();
-        XNAT.xhr.get(XNAT.url.restUrl('/data/version'), timeout.touchCallback);
+        XNAT.xhr.get(XNAT.url.restUrl('/xapi/siteConfig/buildInfo'), timeout.touchCallback);
         $('applet').css('visibility', 'visible');
     };
 
-- 
GitLab