From 2a488ece6fb05a3fb0f5c02e9890a7fa345fc4b2 Mon Sep 17 00:00:00 2001
From: Rick Herrick <jrherrick@wustl.edu>
Date: Mon, 12 Sep 2016 15:23:23 -0500
Subject: [PATCH] XNAT-4499 XNAT-4509 XNAT-4516 Added environment to
 XnatAppInfo to support flexible properties (specifically xnat.is-primary-node
 setting). Added property helper methods as well. Added
 StringHttpMessageConverter to allow returning strings as XML, JSON, etc.

---
 .../org/nrg/xnat/configuration/WebConfig.java |  2 +
 .../nrg/xnat/initialization/RootConfig.java   |  9 +-
 .../tasks/MigrateDatabaseTables.java          |  9 +-
 .../org/nrg/xnat/services/XnatAppInfo.java    | 91 ++++++++++++++++---
 4 files changed, 92 insertions(+), 19 deletions(-)

diff --git a/src/main/java/org/nrg/xnat/configuration/WebConfig.java b/src/main/java/org/nrg/xnat/configuration/WebConfig.java
index 0046e1c8..b560e3b5 100644
--- a/src/main/java/org/nrg/xnat/configuration/WebConfig.java
+++ b/src/main/java/org/nrg/xnat/configuration/WebConfig.java
@@ -14,6 +14,7 @@ import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Import;
 import org.springframework.context.support.ReloadableResourceBundleMessageSource;
 import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.StringHttpMessageConverter;
 import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
 import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
 import org.springframework.http.converter.xml.MarshallingHttpMessageConverter;
@@ -62,6 +63,7 @@ public class WebConfig extends WebMvcConfigurerAdapter {
     public void configureMessageConverters(final List<HttpMessageConverter<?>> converters) {
         converters.add(new MappingJackson2HttpMessageConverter(_objectMapperBuilder.build()));
         converters.add(new MarshallingHttpMessageConverter(_marshaller, _marshaller));
+        converters.add(new StringHttpMessageConverter());
     }
 
     @Bean
diff --git a/src/main/java/org/nrg/xnat/initialization/RootConfig.java b/src/main/java/org/nrg/xnat/initialization/RootConfig.java
index 629dc908..d886b31e 100644
--- a/src/main/java/org/nrg/xnat/initialization/RootConfig.java
+++ b/src/main/java/org/nrg/xnat/initialization/RootConfig.java
@@ -20,15 +20,12 @@ import org.nrg.xnat.services.XnatAppInfo;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Import;
+import org.springframework.core.env.Environment;
 import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
-import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
-import org.springframework.http.converter.xml.MarshallingHttpMessageConverter;
 import org.springframework.jdbc.core.JdbcTemplate;
-import org.springframework.oxm.jaxb.Jaxb2Marshaller;
 import org.springframework.scheduling.concurrent.ThreadPoolExecutorFactoryBean;
 
 import javax.servlet.ServletContext;
-import javax.xml.bind.Marshaller;
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
@@ -51,8 +48,8 @@ import java.util.Properties;
 @Import({PropertiesConfig.class, DatabaseConfig.class, SecurityConfig.class, ApplicationConfig.class})
 public class RootConfig {
     @Bean
-    public XnatAppInfo appInfo(final SiteConfigPreferences preferences, final ServletContext context, final SerializerService serializerService, final JdbcTemplate template) throws IOException {
-        return new XnatAppInfo(preferences, context, serializerService, template);
+    public XnatAppInfo appInfo(final SiteConfigPreferences preferences, final ServletContext context, final Environment environment, final SerializerService serializerService, final JdbcTemplate template) throws IOException {
+        return new XnatAppInfo(preferences, context, environment, serializerService, template);
     }
 
     @Bean
diff --git a/src/main/java/org/nrg/xnat/initialization/tasks/MigrateDatabaseTables.java b/src/main/java/org/nrg/xnat/initialization/tasks/MigrateDatabaseTables.java
index c950f6ce..3433bad5 100644
--- a/src/main/java/org/nrg/xnat/initialization/tasks/MigrateDatabaseTables.java
+++ b/src/main/java/org/nrg/xnat/initialization/tasks/MigrateDatabaseTables.java
@@ -11,6 +11,7 @@ package org.nrg.xnat.initialization.tasks;
 import com.google.common.base.Joiner;
 import org.nrg.framework.orm.DatabaseHelper;
 import org.nrg.framework.utilities.BasicXnatResourceLocator;
+import org.nrg.xnat.services.XnatAppInfo;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -30,9 +31,10 @@ import java.util.Properties;
 @Component
 public class MigrateDatabaseTables extends AbstractInitializingTask {
     @Autowired
-    public MigrateDatabaseTables(final JdbcTemplate template, final TransactionTemplate transactionTemplate) {
+    public MigrateDatabaseTables(final JdbcTemplate template, final TransactionTemplate transactionTemplate, final XnatAppInfo appInfo) {
         super();
         _db = new DatabaseHelper(template, transactionTemplate);
+        _appInfo = appInfo;
     }
 
     @Override
@@ -86,6 +88,10 @@ public class MigrateDatabaseTables extends AbstractInitializingTask {
                     }
                 }
             }
+            if (_appInfo.isPrimaryNode()) {
+                _log.info("This service is the primary XNAT node, checking whether database updates are required.");
+                // Do the needful here.
+            }
             complete();
         } catch (IOException e) {
             _log.error("An error occurred attempting to read table migration properties files", e);
@@ -98,4 +104,5 @@ public class MigrateDatabaseTables extends AbstractInitializingTask {
     private static final String SQL_WARNING_TABLE = "The requested table";
 
     private final DatabaseHelper _db;
+    private final XnatAppInfo _appInfo;
 }
diff --git a/src/main/java/org/nrg/xnat/services/XnatAppInfo.java b/src/main/java/org/nrg/xnat/services/XnatAppInfo.java
index 4559c9f8..4d3b80bb 100644
--- a/src/main/java/org/nrg/xnat/services/XnatAppInfo.java
+++ b/src/main/java/org/nrg/xnat/services/XnatAppInfo.java
@@ -7,6 +7,7 @@ import org.nrg.prefs.exceptions.InvalidPreferenceName;
 import org.nrg.xdat.preferences.SiteConfigPreferences;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.core.env.Environment;
 import org.springframework.core.io.DefaultResourceLoader;
 import org.springframework.core.io.Resource;
 import org.springframework.core.io.ResourceLoader;
@@ -34,7 +35,8 @@ import java.util.regex.Pattern;
 @Component
 public class XnatAppInfo {
 
-    public static final String NON_RELEASE_VERSION_REGEX = "(?i:^.*(SNAPSHOT|BETA|RC).*$)";
+    public static final String NON_RELEASE_VERSION_REGEX  = "(?i:^.*(SNAPSHOT|BETA|RC).*$)";
+    public static final String XNAT_PRIMARY_MODE_PROPERTY = "xnat.is_primary_node";
 
     private static final int           MILLISECONDS_IN_A_DAY    = (24 * 60 * 60 * 1000);
     private static final int           MILLISECONDS_IN_AN_HOUR  = (60 * 60 * 1000);
@@ -46,9 +48,11 @@ public class XnatAppInfo {
     private static final String        SECONDS                  = "seconds";
 
     @Inject
-    public XnatAppInfo(final SiteConfigPreferences preferences, final ServletContext context, final SerializerService serializerService, final JdbcTemplate template) throws IOException {
-        _preferences=preferences;
+    public XnatAppInfo(final SiteConfigPreferences preferences, final ServletContext context, final Environment environment, final SerializerService serializerService, final JdbcTemplate template) throws IOException {
+        _preferences = preferences;
         _template = template;
+        _environment = environment;
+        _primaryNode = Boolean.parseBoolean(_environment.getProperty(XNAT_PRIMARY_MODE_PROPERTY, "true"));
 
         final Resource configuredUrls = RESOURCE_LOADER.getResource("classpath:META-INF/xnat/security/configured-urls.yaml");
         try (final InputStream inputStream = configuredUrls.getInputStream()) {
@@ -164,11 +168,11 @@ public class XnatAppInfo {
                         _log.info("The site was not flagged as initialized and initialized preference set to false. Setting system for initialization.");
                     }
                     for (String pref : _foundPreferences.keySet()) {
-                        if(_foundPreferences.get(pref)!=null) {
+                        if (_foundPreferences.get(pref) != null) {
                             _template.update(
                                     "UPDATE xhbm_preference SET value = ? WHERE name = ?",
                                     new Object[]{_foundPreferences.get(pref), pref}, new int[]{Types.VARCHAR, Types.VARCHAR}
-                            );
+                                            );
                             try {
                                 _preferences.set(_foundPreferences.get(pref), pref);
                             } catch (InvalidPreferenceName e) {
@@ -176,9 +180,8 @@ public class XnatAppInfo {
                             } catch (NullPointerException e) {
                                 _log.error("Error getting site config preferences.", e);
                             }
-                        }
-                        else{
-                            _log.warn("Preference "+pref+" was null.");
+                        } else {
+                            _log.warn("Preference " + pref + " was null.");
                         }
                     }
                 }
@@ -210,6 +213,56 @@ public class XnatAppInfo {
         return (Properties) _properties.clone();
     }
 
+    /**
+     * Gets the requested environment property. Returns null if the property doesn't exist in the environment.
+     *
+     * @param property The name of the property to retrieve.
+     *
+     * @return The value of the property if found, null otherwise.
+     */
+    public String getConfiguredProperty(final String property) {
+        return getConfiguredProperty(property, (String) null);
+    }
+
+    /**
+     * Gets the requested environment property. Returns the specified default value if the property doesn't exist in the
+     * environment.
+     *
+     * @param property     The name of the property to retrieve.
+     * @param defaultValue The default value to return if the property isn't set in the environment.
+     *
+     * @return The value of the property if found, the specified default value otherwise.
+     */
+    public String getConfiguredProperty(final String property, final String defaultValue) {
+        return _environment.getProperty(property, defaultValue);
+    }
+
+    /**
+     * Gets the requested environment property. Returns null if the property doesn't exist in the environment.
+     *
+     * @param property The name of the property to retrieve.
+     * @param type     The type of the property to retrieve.
+     *
+     * @return The value of the property if found, null otherwise.
+     */
+    public <T> T getConfiguredProperty(final String property, final Class<T> type) {
+        return getConfiguredProperty(property, type, null);
+    }
+
+    /**
+     * Gets the requested environment property. Returns the specified default value if the property doesn't exist in the
+     * environment.
+     *
+     * @param property     The name of the property to retrieve.
+     * @param type     The type of the property to retrieve.
+     * @param defaultValue The default value to return if the property isn't set in the environment.
+     *
+     * @return The value of the property if found, the specified default value otherwise.
+     */
+    public <T> T getConfiguredProperty(final String property, final Class<T> type, final T defaultValue) {
+        return _environment.getProperty(property, type, defaultValue);
+    }
+
     /**
      * Gets the version of the application.
      *
@@ -297,6 +350,17 @@ public class XnatAppInfo {
         return uptime;
     }
 
+    /**
+     * Indicates whether this is a stand-alone XNAT server or the primary node in a distributed XNAT deployment, as
+     * opposed to a secondary node. The return value for this method is determined by the value set for the
+     * <b>xnat.is_primary_node</b> property. If no value is set for this property, it defaults to <b>true</b>.
+     *
+     * @return Returns true if this is a stand-alone XNAT server or the primary node in a distributed XNAT deployment.
+     */
+    public boolean isPrimaryNode() {
+        return _primaryNode;
+    }
+
     /**
      * Returns the system uptime in a formatted display string.
      *
@@ -431,14 +495,17 @@ public class XnatAppInfo {
     private static final ResourceLoader RESOURCE_LOADER             = new DefaultResourceLoader();
 
     private final JdbcTemplate _template;
+    private final Environment  _environment;
 
-    private final String _configPath;
-    private final Pattern _configPathPattern;
+    private final String                _configPath;
+    private final Pattern               _configPathPattern;
     private final AntPathRequestMatcher _configPathMatcher;
-    private final String _nonAdminErrorPath;
-    private final Pattern _nonAdminErrorPathPattern;
+    private final String                _nonAdminErrorPath;
+    private final Pattern               _nonAdminErrorPathPattern;
     private final AntPathRequestMatcher _nonAdminErrorPathMatcher;
     private final SiteConfigPreferences _preferences;
+    private final boolean               _primaryNode;
+
     private final Map<String, AntPathRequestMatcher> _openUrls         = new HashMap<>();
     private final Map<String, AntPathRequestMatcher> _adminUrls        = new HashMap<>();
     private final Map<String, AntPathRequestMatcher> _initPaths        = new HashMap<>();
-- 
GitLab