From f4e4687a8156b489fb4c73146c04b3fdf15f6a9d Mon Sep 17 00:00:00 2001
From: Rick Herrick <jrherrick@wustl.edu>
Date: Mon, 25 Jul 2016 14:52:22 -0500
Subject: [PATCH] XNAT-4349 Cleaned up dependency issues that blocked REST
 implementation.

---
 .../nrg/xapi/rest/event/EventHandlerApi.java  |  58 +--
 .../rest/notifications/NotificationsApi.java  | 438 +++++++++++++-----
 .../org/nrg/xnat/configuration/WebConfig.java |  26 +-
 .../org/nrg/xnat/services/XnatAppInfo.java    |  72 ++-
 .../org/nrg/xnat/messages/system.properties   |   9 +
 5 files changed, 430 insertions(+), 173 deletions(-)

diff --git a/src/main/java/org/nrg/xapi/rest/event/EventHandlerApi.java b/src/main/java/org/nrg/xapi/rest/event/EventHandlerApi.java
index e2922e1b..6d941352 100644
--- a/src/main/java/org/nrg/xapi/rest/event/EventHandlerApi.java
+++ b/src/main/java/org/nrg/xapi/rest/event/EventHandlerApi.java
@@ -70,7 +70,7 @@ public class EventHandlerApi extends AbstractXapiRestController {
      *
      * @return the response entity
      */
-    @ApiOperation(value = "Get list of event classes.", notes = "Returns a list of classes implementing AutomationEventI.", response = List.class)
+    @ApiOperation(value = "Get list of event classes.", notes = "Returns a list of classes implementing AutomationEventI.", response = EventClassInfo.class, responseContainer = "List")
     @ApiResponses({@ApiResponse(code = 200, message = "An array of class names"), @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"/projects/{project_id}/eventHandlers/automationEventClasses"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = RequestMethod.GET)
     @ResponseBody
@@ -92,7 +92,7 @@ public class EventHandlerApi extends AbstractXapiRestController {
      *
      * @return the response entity
      */
-    @ApiOperation(value = "Get list of event classes.", notes = "Returns a list of classes implementing AutomationEventI.", response = List.class)
+    @ApiOperation(value = "Get list of event classes.", notes = "Returns a list of classes implementing AutomationEventI.", response = EventClassInfo.class, responseContainer = "List")
     @ApiResponses({@ApiResponse(code = 200, message = "An array of class names"), @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"/eventHandlers/automationEventClasses"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = RequestMethod.GET)
     @ResponseBody
@@ -109,6 +109,33 @@ public class EventHandlerApi extends AbstractXapiRestController {
         }
     }
 
+    /**
+     * Checks if is permitted.
+     *
+     * @param projectId the project ID
+     *
+     * @return the http status
+     */
+    // TODO: Migrate this to the abstract superclass. Can't right now because XDAT doesn't know about XnatProjectdata, etc.
+    protected HttpStatus canEditProject(String projectId) {
+        final UserI sessionUser = getSessionUser();
+        if ((sessionUser instanceof XDATUser)) {
+            if (projectId != null) {
+                final XnatProjectdata project = AutoXnatProjectdata.getXnatProjectdatasById(projectId, sessionUser, false);
+                try {
+                    return Permissions.canEdit(sessionUser, project) ? null : HttpStatus.FORBIDDEN;
+                } catch (Exception e) {
+                    _log.error("Error checking read status for project", e);
+                    return HttpStatus.INTERNAL_SERVER_ERROR;
+                }
+            } else {
+                return isPermitted() == null ? null : HttpStatus.FORBIDDEN;
+            }
+        }
+        _log.error("Error checking read status for project");
+        return HttpStatus.INTERNAL_SERVER_ERROR;
+    }
+
     /**
      * Gets the event info list.
      *
@@ -251,33 +278,6 @@ public class EventHandlerApi extends AbstractXapiRestController {
         return classList;
     }
 
-    /**
-     * Checks if is permitted.
-     *
-     * @param projectId the project ID
-     *
-     * @return the http status
-     */
-    // TODO: Migrate this to the abstract superclass. Can't right now because XDAT doesn't know about XnatProjectdata, etc.
-    protected HttpStatus canEditProject(String projectId) {
-        final UserI sessionUser = getSessionUser();
-        if ((sessionUser instanceof XDATUser)) {
-            if (projectId != null) {
-                final XnatProjectdata project = AutoXnatProjectdata.getXnatProjectdatasById(projectId, sessionUser, false);
-                try {
-                    return Permissions.canEdit(sessionUser, project) ? null : HttpStatus.FORBIDDEN;
-                } catch (Exception e) {
-                    _log.error("Error checking read status for project", e);
-                    return HttpStatus.INTERNAL_SERVER_ERROR;
-                }
-            } else {
-                return isPermitted() == null ? null : HttpStatus.FORBIDDEN;
-            }
-        }
-        _log.error("Error checking read status for project");
-        return HttpStatus.INTERNAL_SERVER_ERROR;
-    }
-
     /**
      * The Constant _log.
      */
diff --git a/src/main/java/org/nrg/xapi/rest/notifications/NotificationsApi.java b/src/main/java/org/nrg/xapi/rest/notifications/NotificationsApi.java
index 314181db..6602ec46 100644
--- a/src/main/java/org/nrg/xapi/rest/notifications/NotificationsApi.java
+++ b/src/main/java/org/nrg/xapi/rest/notifications/NotificationsApi.java
@@ -5,6 +5,7 @@ import org.apache.commons.lang3.StringUtils;
 import org.nrg.framework.annotations.XapiRestController;
 import org.nrg.framework.exceptions.NrgServiceError;
 import org.nrg.framework.exceptions.NrgServiceRuntimeException;
+import org.nrg.framework.services.SerializerService;
 import org.nrg.prefs.exceptions.InvalidPreferenceName;
 import org.nrg.xapi.exceptions.InitializationException;
 import org.nrg.xdat.preferences.NotificationsPreferences;
@@ -23,6 +24,7 @@ import org.springframework.web.bind.annotation.*;
 
 import javax.inject.Inject;
 import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Properties;
@@ -48,17 +50,21 @@ public class NotificationsApi extends AbstractXapiRestController {
                                                        + "by calling the POST version of this method.";
 
     @Inject
-    public NotificationsApi(final UserManagementServiceI userManagementService, final RoleHolder roleHolder, final NotificationsPreferences notificationsPrefs, final JavaMailSenderImpl javaMailSender, final XnatAppInfo appInfo) {
+    public NotificationsApi(final UserManagementServiceI userManagementService, final RoleHolder roleHolder, final NotificationsPreferences notificationsPrefs, final JavaMailSenderImpl javaMailSender, final XnatAppInfo appInfo, final SerializerService serializer) {
         super(userManagementService, roleHolder);
         _notificationsPrefs = notificationsPrefs;
         _javaMailSender = javaMailSender;
         _appInfo = appInfo;
+        _serializer = serializer;
     }
 
-    @ApiOperation(value = "Returns the full map of site configuration properties.", notes = "Complex objects may be returned as encapsulated JSON strings.", response = String.class, responseContainer = "Map")
-    @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")})
+    @ApiOperation(value = "Returns the full map of site configuration properties.", notes = "Complex objects may be returned as encapsulated JSON strings.", response = Properties.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})
-    public ResponseEntity<Map<String, Object>> getAllSiteConfigProperties(final HttpServletRequest request) {
+    public ResponseEntity<Properties> getAllSiteConfigProperties(@ApiParam(hidden = true) @RequestParam final HttpServletRequest request) throws IOException {
         final HttpStatus status = isPermitted();
         if (status != null) {
             return new ResponseEntity<>(status);
@@ -68,7 +74,7 @@ public class NotificationsApi extends AbstractXapiRestController {
             _log.debug("User " + username + " requested the site configuration.");
         }
 
-        final Map<String, Object> preferences = _notificationsPrefs.getPreferenceMap();
+        final Properties preferences = convertToProperties(_notificationsPrefs.getPreferenceMap());
 
         if (!_appInfo.isInitialized()) {
             if (_log.isInfoEnabled()) {
@@ -86,9 +92,12 @@ public class NotificationsApi extends AbstractXapiRestController {
     }
 
     @ApiOperation(value = "Sets a map of notifications properties.", notes = "Sets the notifications properties specified in the map.", response = Void.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "Notifications properties successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set notifications properties."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiResponses({@ApiResponse(code = 200, message = "Notifications properties successfully set."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to set notifications properties."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = "batch", consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE, MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.POST})
-    public ResponseEntity<Void> setBatchNotificationsProperties(@ApiParam(value = "The map of notifications properties to be set.", required = true) @RequestBody final Map<String, String> properties) throws InitializationException {
+    public ResponseEntity<Void> setBatchNotificationsProperties(@ApiParam(value = "The map of notifications properties to be set.", required = true) @RequestBody final Properties properties) throws InitializationException {
         final HttpStatus status = isPermitted();
         if (status != null) {
             return new ResponseEntity<>(status);
@@ -96,18 +105,18 @@ public class NotificationsApi extends AbstractXapiRestController {
 
         logSetProperties(properties);
 
-        for (final String name : properties.keySet()) {
+        for (final String name : properties.stringPropertyNames()) {
             try {
                 if (StringUtils.equals(name, "notifications.emailRecipientErrorMessages")) {
-                    _notificationsPrefs.setEmailRecipientErrorMessages(properties.get(name));
+                    _notificationsPrefs.setEmailRecipientErrorMessages(properties.getProperty(name));
                 } else if (StringUtils.equals(name, "notifications.emailRecipientIssueReports")) {
-                    _notificationsPrefs.setEmailRecipientIssueReports(properties.get(name));
+                    _notificationsPrefs.setEmailRecipientIssueReports(properties.getProperty(name));
                 } else if (StringUtils.equals(name, "notifications.emailRecipientNewUserAlert")) {
-                    _notificationsPrefs.setEmailRecipientNewUserAlert(properties.get(name));
+                    _notificationsPrefs.setEmailRecipientNewUserAlert(properties.getProperty(name));
                 } else if (StringUtils.equals(name, "notifications.emailRecipientUpdate")) {
-                    _notificationsPrefs.setEmailRecipientUpdate(properties.get(name));
+                    _notificationsPrefs.setEmailRecipientUpdate(properties.getProperty(name));
                 } else {
-                    _notificationsPrefs.set(properties.get(name), name);
+                    _notificationsPrefs.set(properties.getProperty(name), name);
                 }
                 if (_log.isInfoEnabled()) {
                     _log.info("Set property {} to value: {}", name, properties.get(name));
@@ -121,9 +130,12 @@ public class NotificationsApi extends AbstractXapiRestController {
     }
 
     @ApiOperation(value = "Sets a map of notifications properties for mail.", notes = "Sets the notifications properties for mail specified in the map.", response = Void.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "Notifications properties for mail successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set notifications properties for mail."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiResponses({@ApiResponse(code = 200, message = "Notifications properties for mail successfully set."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to set notifications properties for mail."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = "batchMail", consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE, MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.POST})
-    public ResponseEntity<Void> setBatchNotificationsPropertiesForMail(@ApiParam(value = "The map of notifications properties for mail to be set.", required = true) @RequestBody final Map<String, String> properties) throws InitializationException {
+    public ResponseEntity<Void> setBatchNotificationsPropertiesForMail(@ApiParam(value = "The map of notifications properties for mail to be set.", required = true) @RequestBody final Properties properties) throws InitializationException {
         final HttpStatus status = isPermitted();
         if (status != null) {
             return new ResponseEntity<>(status);
@@ -131,9 +143,9 @@ public class NotificationsApi extends AbstractXapiRestController {
 
         logSetProperties(properties);
 
-        for (final String name : properties.keySet()) {
+        for (final String name : properties.stringPropertyNames()) {
             try {
-                _notificationsPrefs.set(properties.get(name), name);
+                _notificationsPrefs.set(properties.getProperty(name), name);
                 if (_log.isInfoEnabled()) {
                     _log.info("Set property {} to value: {}", name, properties.get(name));
                 }
@@ -142,8 +154,8 @@ public class NotificationsApi extends AbstractXapiRestController {
             }
         }
 
-        String host = _notificationsPrefs.getHostname();
-        int port = _notificationsPrefs.getPort();
+        String host     = _notificationsPrefs.getHostname();
+        int    port     = _notificationsPrefs.getPort();
         String protocol = _notificationsPrefs.getProtocol();
         String username = _notificationsPrefs.getUsername();
         String password = _notificationsPrefs.getPassword();
@@ -163,8 +175,13 @@ public class NotificationsApi extends AbstractXapiRestController {
         return new ResponseEntity<>(HttpStatus.OK);
     }
 
-    @ApiOperation(value = "Returns the full SMTP server configuration.", notes = "Returns the configuration as a map of the standard Java mail sender properties&ndash;host, port, protocol, username, and password&ndash;along with any extended properties required for the configuration, e.g. configuring SSL- or TLS-secured SMTP services.", response = Properties.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "SMTP service 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")})
+    @ApiOperation(value = "Returns the full SMTP server configuration.",
+                  notes = "Returns the configuration as a map of the standard Java mail sender properties&ndash;host, port, protocol, username, and password&ndash;along with any extended properties required for the configuration, e.g. configuring SSL- or TLS-secured SMTP services.",
+                  response = Properties.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "SMTP service 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(value = "smtp", produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET})
     public ResponseEntity<Properties> getSmtpServerProperties() {
         final HttpStatus status = isPermitted();
@@ -198,15 +215,20 @@ public class NotificationsApi extends AbstractXapiRestController {
         return new ResponseEntity<>(properties, HttpStatus.OK);
     }
 
-    @ApiOperation(value = "Sets the mail service properties. This return the SMTP server configuration as it exists after being set.", notes = POST_PROPERTIES_NOTES, response = Properties.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "Mail service properties successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set the mail service properties."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Sets the mail service properties. This return the SMTP server configuration as it exists after being set.",
+                  notes = POST_PROPERTIES_NOTES,
+                  response = Properties.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Mail service properties successfully set."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to set the mail service properties."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"smtp"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.POST})
     public ResponseEntity<Properties> setAllMailProperties(@ApiParam("The value to set for the email host.") @RequestParam(value = "host", required = false, defaultValue = NOT_SET) final String host,
                                                            @ApiParam("The value to set for the email port.") @RequestParam(value = "port", required = false, defaultValue = "-1") final int port,
                                                            @ApiParam("The value to set for the email username.") @RequestParam(value = "username", required = false, defaultValue = NOT_SET) final String username,
                                                            @ApiParam("The value to set for the email password.") @RequestParam(value = "password", required = false, defaultValue = NOT_SET) final String password,
                                                            @ApiParam("The value to set for the email protocol.") @RequestParam(value = "protocol", required = false, defaultValue = NOT_SET) final String protocol,
-                                                           @ApiParam(value = "Values to set for extra mail properties. An empty value indicates that an existing property should be removed.") @RequestParam final Map<String, String> properties) {
+                                                           @ApiParam("Values to set for extra mail properties. An empty value indicates that an existing property should be removed.") @RequestParam final Properties properties) {
         final HttpStatus status = isPermitted();
         if (status != null) {
             return new ResponseEntity<>(status);
@@ -228,38 +250,20 @@ public class NotificationsApi extends AbstractXapiRestController {
         return getSmtpServerProperties();
     }
 
-    protected Properties getSubmittedProperties(final Map<String, String> properties) {
-        // Set all of the submitted properties.
-        final Properties javaMailProperties = new Properties();
-        if (properties != null) {
-            for (final String property : properties.keySet()) {
-                final String value = properties.get(property);
-                if (StringUtils.isNotBlank(value)) {
-                    javaMailProperties.setProperty(property, value);
-                }
-            }
-        }
-
-        // Find any existing properties that weren't submitted...
-        final Properties existing = _javaMailSender.getJavaMailProperties();
-        for (final String property : existing.stringPropertyNames()) {
-            if (!javaMailProperties.containsKey(property)) {
-                // Set the value to "", this will remove the property.
-                javaMailProperties.setProperty(property, "");
-            }
-        }
-        return javaMailProperties;
-    }
-
-    @ApiOperation(value = "Sets the submitted mail service properties. This returns the SMTP server configuration as it exists after being set.", notes = PUT_PROPERTIES_NOTES, response = Properties.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "Mail service properties successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set the mail service properties."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Sets the submitted mail service properties. This returns the SMTP server configuration as it exists after being set.",
+                  notes = PUT_PROPERTIES_NOTES,
+                  response = Properties.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Mail service properties successfully set."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to set the mail service properties."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"smtp"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT})
     public ResponseEntity<Properties> setSubmittedMailProperties(@ApiParam("The value to set for the email host.") @RequestParam(value = "host", required = false, defaultValue = NOT_SET) final String host,
                                                                  @ApiParam("The value to set for the email port.") @RequestParam(value = "port", required = false, defaultValue = "-1") final int port,
                                                                  @ApiParam("The value to set for the email username.") @RequestParam(value = "username", required = false, defaultValue = NOT_SET) final String username,
                                                                  @ApiParam("The value to set for the email password.") @RequestParam(value = "password", required = false, defaultValue = NOT_SET) final String password,
                                                                  @ApiParam("The value to set for the email protocol.") @RequestParam(value = "protocol", required = false, defaultValue = NOT_SET) final String protocol,
-                                                                 @ApiParam(value = "Values to set for extra mail properties. An empty value indicates that an existing property should be removed.") @RequestParam final Map<String, String> properties) {
+                                                                 @ApiParam("Values to set for extra mail properties. An empty value indicates that an existing property should be removed.") @RequestParam final Properties properties) {
         final HttpStatus status = isPermitted();
         if (status != null) {
             return new ResponseEntity<>(status);
@@ -273,8 +277,8 @@ public class NotificationsApi extends AbstractXapiRestController {
         setUsername(username);
         setPassword(password);
         if (properties != null) {
-            for (final String property : properties.keySet()) {
-                _javaMailSender.getJavaMailProperties().setProperty(property, properties.get(property));
+            for (final String property : properties.stringPropertyNames()) {
+                _javaMailSender.getJavaMailProperties().setProperty(property, properties.getProperty(property));
             }
         }
 
@@ -283,8 +287,13 @@ public class NotificationsApi extends AbstractXapiRestController {
         return getSmtpServerProperties();
     }
 
-    @ApiOperation(value = "Sets the mail service host.", notes = "Sets the mail service host.", response = Properties.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "Mail service host successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set the mail service host."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Sets the mail service host.",
+                  notes = "Sets the mail service host.",
+                  response = Properties.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Mail service host successfully set."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to set the mail service host."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"smtp/host/{host}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT})
     public ResponseEntity<Properties> setHostProperty(@ApiParam(value = "The value to set for the email host.", required = true) @PathVariable final String host) {
         final HttpStatus status = isPermitted();
@@ -299,8 +308,13 @@ public class NotificationsApi extends AbstractXapiRestController {
         return getSmtpServerProperties();
     }
 
-    @ApiOperation(value = "Sets the mail service port.", notes = "Sets the mail service port.", response = Properties.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "Mail service port successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set the mail service port."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Sets the mail service port.",
+                  notes = "Sets the mail service port.",
+                  response = Properties.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Mail service port successfully set."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to set the mail service port."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"smtp/port/{port}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT})
     public ResponseEntity<Properties> setPortProperty(@ApiParam(value = "The value to set for the email port.", required = true) @PathVariable final int port) {
         final HttpStatus status = isPermitted();
@@ -315,8 +329,13 @@ public class NotificationsApi extends AbstractXapiRestController {
         return getSmtpServerProperties();
     }
 
-    @ApiOperation(value = "Sets the mail service protocol.", notes = "Sets the mail service protocol.", response = Properties.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "Mail service protocol successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set the mail service protocol."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Sets the mail service protocol.",
+                  notes = "Sets the mail service protocol.",
+                  response = Properties.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Mail service protocol successfully set."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to set the mail service protocol."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"smtp/protocol/{protocol}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT})
     public ResponseEntity<Properties> setProtocolProperty(@ApiParam(value = "The value to set for the email protocol.", required = true) @PathVariable final String protocol) {
         final HttpStatus status = isPermitted();
@@ -330,8 +349,13 @@ public class NotificationsApi extends AbstractXapiRestController {
         return getSmtpServerProperties();
     }
 
-    @ApiOperation(value = "Sets the mail service username.", notes = "Sets the mail service username.", response = Properties.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "Mail service username successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set the mail service username."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Sets the mail service username.",
+                  notes = "Sets the mail service username.",
+                  response = Properties.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Mail service username successfully set."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to set the mail service username."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"smtp/username/{username}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT})
     public ResponseEntity<Properties> setUsernameProperty(@ApiParam(value = "The value to set for the email username.", required = true) @PathVariable final String username) {
         final HttpStatus status = isPermitted();
@@ -345,8 +369,13 @@ public class NotificationsApi extends AbstractXapiRestController {
         return getSmtpServerProperties();
     }
 
-    @ApiOperation(value = "Sets the mail service password.", notes = "Sets the mail service password.", response = Properties.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "Mail service password successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set the mail service password."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Sets the mail service password.",
+                  notes = "Sets the mail service password.",
+                  response = Properties.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Mail service password successfully set."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to set the mail service password."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"smtp/password/{password}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT})
     public ResponseEntity<Properties> setPasswordProperty(@ApiParam(value = "The value to set for the email password.", required = true) @PathVariable final String password) {
         final HttpStatus status = isPermitted();
@@ -360,8 +389,13 @@ public class NotificationsApi extends AbstractXapiRestController {
         return getSmtpServerProperties();
     }
 
-    @ApiOperation(value = "Sets a Java mail property with the submitted name and value.", notes = "Setting a property to an empty value will remove the property.", response = Properties.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "Mail service password successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set the mail service password."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Sets a Java mail property with the submitted name and value.",
+                  notes = "Setting a property to an empty value will remove the property.",
+                  response = Properties.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Mail service password successfully set."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to set the mail service password."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"smtp/property/{property}/{value}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT})
     public ResponseEntity<Properties> setExtendedProperty(@ApiParam(value = "The value to set for the email password.", required = true) @PathVariable final String property,
                                                           @ApiParam(value = "The value to set for the email password.", required = true) @PathVariable final String value) {
@@ -376,203 +410,366 @@ public class NotificationsApi extends AbstractXapiRestController {
         return getSmtpServerProperties();
     }
 
-    @ApiOperation(value = "Sets the email message for contacting help.", notes = "Sets the email message that people should receive when contacting help.", response = String.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "Help email message successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set the help email message."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Sets the email message for contacting help.",
+                  notes = "Sets the email message that people should receive when contacting help.",
+                  response = Void.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Help email message successfully set."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to set the help email message."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"messages/help"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.POST})
     public ResponseEntity<Void> setHelpContactInfo(@ApiParam(value = "The email message for contacting help.", required = true) @RequestParam final String message) {
         _notificationsPrefs.setHelpContactInfo(message);
         return new ResponseEntity<>(HttpStatus.OK);
     }
 
-    @ApiOperation(value = "Sets the email message for user registration.", notes = "Sets the email message that people should receive when they register. Link for email validation is auto-populated.", response = String.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "User registration email message successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set the user registration email message."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Sets the email message for user registration.",
+                  notes = "Sets the email message that people should receive when they register. Link for email validation is auto-populated.",
+                  response = Properties.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "User registration email message successfully set."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to set the user registration email message."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"messages/registration"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.POST})
     public ResponseEntity<Properties> setEmailMessageUserRegistration(@ApiParam(value = "The email message for user registration.", required = true) @RequestParam final String message) {
         _notificationsPrefs.setEmailMessageUserRegistration(message);
         return new ResponseEntity<>(HttpStatus.OK);
     }
 
-    @ApiOperation(value = "Sets the email message for forgot username.", notes = "Sets the email message that people should receive when they click that they forgot their username.", response = String.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "Forgot username email message successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set the forgot username email message."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Sets the email message for forgot username.",
+                  notes = "Sets the email message that people should receive when they click that they forgot their username.",
+                  response = Properties.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Forgot username email message successfully set."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to set the forgot username email message."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"messages/forgotusername"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.POST})
     public ResponseEntity<Properties> setEmailMessageForgotUsernameRequest(@ApiParam(value = "The email message for forgot username.", required = true) @RequestParam final String message) {
         _notificationsPrefs.setEmailMessageForgotUsernameRequest(message);
         return new ResponseEntity<>(HttpStatus.OK);
     }
 
-    @ApiOperation(value = "Sets the email message for password reset.", notes = "Sets the email message that people should receive when they click to reset their password.  Link for password reset is auto-populated.", response = String.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "Password reset message successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set the password reset message."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Sets the email message for password reset.",
+                  notes = "Sets the email message that people should receive when they click to reset their password.  Link for password reset is auto-populated.",
+                  response = Properties.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Password reset message successfully set."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to set the password reset message."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"messages/passwordreset"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.POST})
     public ResponseEntity<Properties> setEmailMessageForgotPasswordReset(@ApiParam(value = "The email message for password reset.", required = true) @RequestParam final String message) {
         _notificationsPrefs.setEmailMessageForgotPasswordReset(message);
         return new ResponseEntity<>(HttpStatus.OK);
     }
 
-    @ApiOperation(value = "Returns the email message for contacting help.", notes = "This returns the email message that people should receive when contacting help.", response = String.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "Email message for contacting help successfully returned."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to get email message for contacting help."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Returns the email message for contacting help.",
+                  notes = "This returns the email message that people should receive when contacting help.",
+                  response = String.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Email message for contacting help successfully returned."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to get email message for contacting help."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"messages/help"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET})
     public ResponseEntity<String> getHelpContactInfo() {
         return new ResponseEntity<>(_notificationsPrefs.getHelpContactInfo(), HttpStatus.OK);
     }
 
-    @ApiOperation(value = "Returns the email message for user registration.", notes = "This returns the email message that people should receive when they register. Link for email validation is auto-populated.", response = String.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "Email message for user registration successfully returned."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to get email message for user registration."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Returns the email message for user registration.",
+                  notes = "This returns the email message that people should receive when they register. Link for email validation is auto-populated.",
+                  response = String.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Email message for user registration successfully returned."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to get email message for user registration."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"messages/registration"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET})
     public ResponseEntity<String> getEmailMessageUserRegistration() {
         return new ResponseEntity<>(_notificationsPrefs.getEmailMessageUserRegistration(), HttpStatus.OK);
     }
 
-    @ApiOperation(value = "Returns the email message for forgot username.", notes = "This returns the email message that people should receive when they click that they forgot their username.", response = String.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "Email message for forgot username successfully returned."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to get email message for forgot username."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Returns the email message for forgot username.",
+                  notes = "This returns the email message that people should receive when they click that they forgot their username.",
+                  response = String.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Email message for forgot username successfully returned."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to get email message for forgot username."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"messages/forgotusername"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET})
     public ResponseEntity<String> getEmailMessageForgotUsernameRequest() {
         return new ResponseEntity<>(_notificationsPrefs.getEmailMessageForgotUsernameRequest(), HttpStatus.OK);
     }
 
-    @ApiOperation(value = "Returns the email message for password reset.", notes = "This returns the email message that people should receive when they click to reset their password.  Link for password reset is auto-populated.", response = String.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "Email message for password reset successfully returned."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to get email message for password reset."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Returns the email message for password reset.",
+                  notes = "This returns the email message that people should receive when they click to reset their password.  Link for password reset is auto-populated.",
+                  response = String.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Email message for password reset successfully returned."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to get email message for password reset."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"messages/passwordreset"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET})
     public ResponseEntity<String> getEmailMessageForgotPasswordReset() {
         return new ResponseEntity<>(_notificationsPrefs.getEmailMessageForgotPasswordReset(), HttpStatus.OK);
     }
 
-    @ApiOperation(value = "Sets whether admins should be notified of user registration.", notes = "Sets whether admins should be notified of user registration.", response = String.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "Whether admins should be notified of user registration successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set whether admins should be notified of user registration."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Sets whether admins should be notified of user registration.",
+                  notes = "Sets whether admins should be notified of user registration.",
+                  response = Void.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Whether admins should be notified of user registration successfully set."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to set whether admins should be notified of user registration."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"notify/registration"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.POST})
     public ResponseEntity<Void> setNotifyAdminUserRegistration(@ApiParam(value = "Whether admins should be notified of user registration successfully set.", required = true) @RequestParam final boolean notify) {
         _notificationsPrefs.setNotifyAdminUserRegistration(notify);
         return new ResponseEntity<>(HttpStatus.OK);
     }
 
-    @ApiOperation(value = "Sets whether admins should be notified of pipeline processing submit.", notes = "Sets whether admins should be notified of pipeline processing submit.", response = String.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "Whether admins should be notified of pipeline processing submit successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set whether admins should be notified of pipeline processing submit."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Sets whether admins should be notified of pipeline processing submit.",
+                  notes = "Sets whether admins should be notified of pipeline processing submit.",
+                  response = Properties.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Whether admins should be notified of pipeline processing submit successfully set."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to set whether admins should be notified of pipeline processing submit."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"notify/pipeline"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.POST})
     public ResponseEntity<Properties> setNotifyAdminPipelineEmails(@ApiParam(value = "Whether admins should be notified of pipeline processing submit successfully set.", required = true) @RequestParam final boolean notify) {
         _notificationsPrefs.setNotifyAdminPipelineEmails(notify);
         return new ResponseEntity<>(HttpStatus.OK);
     }
 
-    @ApiOperation(value = "Sets whether admins should be notified of project access requests.", notes = "Sets whether admins should be notified of project access requests.", response = String.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "Whether admins should be notified of project access requests successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set whether admins should be notified of project access requests."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Sets whether admins should be notified of project access requests.",
+                  notes = "Sets whether admins should be notified of project access requests.",
+                  response = Properties.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Whether admins should be notified of project access requests successfully set."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to set whether admins should be notified of project access requests."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"notify/par"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.POST})
     public ResponseEntity<Properties> setNotifyAdminProjectAccessRequest(@ApiParam(value = "Whether admins should be notified of project access requests successfully set.", required = true) @RequestParam final boolean notify) {
         _notificationsPrefs.setNotifyAdminProjectAccessRequest(notify);
         return new ResponseEntity<>(HttpStatus.OK);
     }
 
-    @ApiOperation(value = "Sets whether admins should be notified of session transfer.", notes = "Sets whether admins should be notified of session transfer by user.", response = String.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "Whether admins should be notified of session transfer successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set whether admins should be notified of session transfer."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Sets whether admins should be notified of session transfer.",
+                  notes = "Sets whether admins should be notified of session transfer by user.",
+                  response = Properties.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Whether admins should be notified of session transfer successfully set."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to set whether admins should be notified of session transfer."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"notify/transfer"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.POST})
     public ResponseEntity<Properties> setNotifyAdminSessionTransfer(@ApiParam(value = "Whether admins should be notified of session transfer successfully set.", required = true) @RequestParam final boolean notify) {
         _notificationsPrefs.setNotifyAdminSessionTransfer(notify);
         return new ResponseEntity<>(HttpStatus.OK);
     }
 
-    @ApiOperation(value = "Returns whether admins should be notified of user registration.", notes = "This returns whether admins should be notified of user registration.", response = Boolean.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "Email message for contacting help successfully returned."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to get email message for contacting help."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Returns whether admins should be notified of user registration.",
+                  notes = "This returns whether admins should be notified of user registration.",
+                  response = Boolean.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Email message for contacting help successfully returned."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to get email message for contacting help."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"notify/registration"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET})
     public ResponseEntity<Boolean> getNotifyAdminUserRegistration() {
         return new ResponseEntity<>(_notificationsPrefs.getNotifyAdminUserRegistration(), HttpStatus.OK);
     }
 
-    @ApiOperation(value = "Returns whether admins should be notified of pipeline processing submit.", notes = "This returns whether admins should be notified of pipeline processing submit.", response = Boolean.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "Email message for user registration successfully returned."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to get email message for user registration."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Returns whether admins should be notified of pipeline processing submit.",
+                  notes = "This returns whether admins should be notified of pipeline processing submit.",
+                  response = Boolean.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Email message for user registration successfully returned."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to get email message for user registration."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"notify/pipeline"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET})
     public ResponseEntity<Boolean> getNotifyAdminPipelineEmails() {
         return new ResponseEntity<>(_notificationsPrefs.getNotifyAdminPipelineEmails(), HttpStatus.OK);
     }
 
-    @ApiOperation(value = "Returns whether admins should be notified of project access requests.", notes = "This returns whether admins should be notified of project access requests.", response = Boolean.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "Email message for forgot username successfully returned."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to get email message for forgot username."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Returns whether admins should be notified of project access requests.",
+                  notes = "This returns whether admins should be notified of project access requests.",
+                  response = Boolean.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Email message for forgot username successfully returned."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to get email message for forgot username."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"notify/par"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET})
     public ResponseEntity<Boolean> getNotifyAdminProjectAccessRequest() {
         return new ResponseEntity<>(_notificationsPrefs.getNotifyAdminProjectAccessRequest(), HttpStatus.OK);
     }
 
-    @ApiOperation(value = "Returns whether admins should be notified of session transfer.", notes = "This returns whether admins should be notified of session transfer.", response = Boolean.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "Email message for password reset successfully returned."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to get email message for password reset."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Returns whether admins should be notified of session transfer.",
+                  notes = "This returns whether admins should be notified of session transfer.",
+                  response = Boolean.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Email message for password reset successfully returned."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to get email message for password reset."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"notify/transfer"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET})
     public ResponseEntity<Boolean> getNotifyAdminSessionTransfer() {
         return new ResponseEntity<>(_notificationsPrefs.getNotifyAdminSessionTransfer(), HttpStatus.OK);
     }
 
-    @ApiOperation(value = "Sets whether non-users should be able to subscribe to notifications.", notes = "Sets whether non-users should be able to subscribe to notifications.", response = String.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "Whether non-users should be able to subscribe to notifications."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set whether non-users should be able to subscribe to notifications."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Sets whether non-users should be able to subscribe to notifications.",
+                  notes = "Sets whether non-users should be able to subscribe to notifications.",
+                  response = Properties.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Whether non-users should be able to subscribe to notifications."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to set whether non-users should be able to subscribe to notifications."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"allow/nonusersubscribers/{setting}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.POST})
     public ResponseEntity<Properties> setEmailAllowNonuserSubscribers(@ApiParam(value = "Whether non-users should be able to subscribe to notifications.", required = true) @PathVariable final boolean setting) {
         _notificationsPrefs.setEmailAllowNonuserSubscribers(setting);
         return new ResponseEntity<>(HttpStatus.OK);
     }
 
-    @ApiOperation(value = "Returns whether non-users should be able to subscribe to notifications.", notes = "This returns whether non-users should be able to subscribe to notifications.", response = Boolean.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "Whether non-users should be able to subscribe to notifications successfully returned."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to get whether non-users should be able to subscribe to notifications."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Returns whether non-users should be able to subscribe to notifications.",
+                  notes = "This returns whether non-users should be able to subscribe to notifications.",
+                  response = Boolean.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Whether non-users should be able to subscribe to notifications successfully returned."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to get whether non-users should be able to subscribe to notifications."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"allow/nonusersubscribers"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET})
     public ResponseEntity<Boolean> getEmailAllowNonuserSubscribers() {
         return new ResponseEntity<>(_notificationsPrefs.getEmailAllowNonuserSubscribers(), HttpStatus.OK);
     }
 
-    @ApiOperation(value = "Sets the email addresses for error notifications.", notes = "Sets the email addresses that should be subscribed to error notifications.", response = Properties.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "Error subscribers successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set the error subscribers."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Sets the email addresses for error notifications.",
+                  notes = "Sets the email addresses that should be subscribed to error notifications.",
+                  response = Void.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Error subscribers successfully set."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to set the error subscribers."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"subscribers/error"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.POST})
     public ResponseEntity<Void> setErrorSubscribers(@ApiParam(value = "The values to set for email addresses for error notifications.", required = true) @RequestParam final String subscribers) {
         _notificationsPrefs.setEmailRecipientErrorMessages(subscribers);
         return new ResponseEntity<>(HttpStatus.OK);
     }
 
-    @ApiOperation(value = "Sets the email addresses for issue notifications.", notes = "Sets the email addresses that should be subscribed to issue notifications.", response = Properties.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "Issue subscribers successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set the issue subscribers."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Sets the email addresses for issue notifications.",
+                  notes = "Sets the email addresses that should be subscribed to issue notifications.",
+                  response = Properties.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Issue subscribers successfully set."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to set the issue subscribers."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"subscribers/issue"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.POST})
     public ResponseEntity<Properties> setIssueSubscribers(@ApiParam(value = "The values to set for email addresses for issue notifications.", required = true) @RequestParam final String subscribers) {
         _notificationsPrefs.setEmailRecipientIssueReports(subscribers);
         return new ResponseEntity<>(HttpStatus.OK);
     }
 
-    @ApiOperation(value = "Sets the email addresses for new user notifications.", notes = "Sets the email addresses that should be subscribed to new user notifications.", response = Properties.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "New user subscribers successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set the new user subscribers."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Sets the email addresses for new user notifications.",
+                  notes = "Sets the email addresses that should be subscribed to new user notifications.",
+                  response = Properties.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "New user subscribers successfully set."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to set the new user subscribers."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"subscribers/newuser"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.POST})
     public ResponseEntity<Properties> setNewUserSubscribers(@ApiParam(value = "The values to set for email addresses for new user notifications.", required = true) @RequestParam final String subscribers) {
         _notificationsPrefs.setEmailRecipientNewUserAlert(subscribers);
         return new ResponseEntity<>(HttpStatus.OK);
     }
 
-    @ApiOperation(value = "Sets the email addresses for update notifications.", notes = "Sets the email addresses that should be subscribed to update notifications.", response = Properties.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "Update subscribers successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set the update subscribers."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Sets the email addresses for update notifications.",
+                  notes = "Sets the email addresses that should be subscribed to update notifications.",
+                  response = Properties.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Update subscribers successfully set."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to set the update subscribers."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"subscribers/update"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.POST})
     public ResponseEntity<Properties> setUpdateSubscribers(@ApiParam(value = "The values to set for email addresses for update notifications.", required = true) @RequestParam final String subscribers) {
         _notificationsPrefs.setEmailRecipientUpdate(subscribers);
         return new ResponseEntity<>(HttpStatus.OK);
     }
 
-    @ApiOperation(value = "Returns list of email addresses subscribed to error notifications.", notes = "This returns a list of all the email addresses that are subscribed to receive error notifications.", response = String.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "Error notification subscribers successfully returned."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to get subscribers for email notifications."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Returns list of email addresses subscribed to error notifications.",
+                  notes = "This returns a list of all the email addresses that are subscribed to receive error notifications.",
+                  response = String.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Error notification subscribers successfully returned."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to get subscribers for email notifications."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"subscribers/error"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET})
     public ResponseEntity<String> getErrorSubscribers() {
         return new ResponseEntity<>(_notificationsPrefs.getEmailRecipientErrorMessages(), HttpStatus.OK);
     }
 
-    @ApiOperation(value = "Returns list of email addresses subscribed to issue notifications.", notes = "This returns a list of all the email addresses that are subscribed to receive issue notifications.", response = String.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "Issue notification subscribers successfully returned."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to get subscribers for email notifications."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Returns list of email addresses subscribed to issue notifications.",
+                  notes = "This returns a list of all the email addresses that are subscribed to receive issue notifications.",
+                  response = String.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Issue notification subscribers successfully returned."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to get subscribers for email notifications."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"subscribers/issue"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET})
     public ResponseEntity<String> getIssueSubscribers() {
         return new ResponseEntity<>(_notificationsPrefs.getEmailRecipientIssueReports(), HttpStatus.OK);
     }
 
-    @ApiOperation(value = "Returns list of email addresses subscribed to new user notifications.", notes = "This returns a list of all the email addresses that are subscribed to receive new user notifications.", response = String.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "New user notification subscribers successfully returned."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to get subscribers for email notifications."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Returns list of email addresses subscribed to new user notifications.",
+                  notes = "This returns a list of all the email addresses that are subscribed to receive new user notifications.",
+                  response = String.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "New user notification subscribers successfully returned."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to get subscribers for email notifications."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"subscribers/newuser"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET})
     public ResponseEntity<String> getNewUserSubscribers() {
         return new ResponseEntity<>(_notificationsPrefs.getEmailRecipientNewUserAlert(), HttpStatus.OK);
     }
 
-    @ApiOperation(value = "Returns list of email addresses subscribed to update notifications.", notes = "This returns a list of all the email addresses that are subscribed to receive update notifications.", response = String.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "Update notification subscribers successfully returned."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to get subscribers for email notifications."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Returns list of email addresses subscribed to update notifications.",
+                  notes = "This returns a list of all the email addresses that are subscribed to receive update notifications.",
+                  response = String.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Update notification subscribers successfully returned."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to get subscribers for email notifications."),
+                   @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"subscribers/update"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET})
     public ResponseEntity<String> getUpdateSubscribers() {
         return new ResponseEntity<>(_notificationsPrefs.getEmailRecipientUpdate(), HttpStatus.OK);
     }
 
+    protected Properties getSubmittedProperties(final Properties properties) {
+        // Set all of the submitted properties.
+        final Properties javaMailProperties = new Properties();
+        if (properties != null) {
+            for (final String property : properties.stringPropertyNames()) {
+                final String value = properties.getProperty(property);
+                if (StringUtils.isNotBlank(value)) {
+                    javaMailProperties.setProperty(property, value);
+                }
+            }
+        }
+
+        // Find any existing properties that weren't submitted...
+        final Properties existing = _javaMailSender.getJavaMailProperties();
+        for (final String property : existing.stringPropertyNames()) {
+            if (!javaMailProperties.containsKey(property)) {
+                // Set the value to "", this will remove the property.
+                javaMailProperties.setProperty(property, "");
+            }
+        }
+        return javaMailProperties;
+    }
+
+    private Properties convertToProperties(final Map<String, Object> preferenceMap) throws IOException {
+        final Properties properties = new Properties();
+        for (final String key : preferenceMap.keySet()) {
+            final Object object = preferenceMap.get(key);
+            final String value  = object != null ? _serializer.toJson(object) : "";
+            properties.setProperty(key, value);
+        }
+        return properties;
+    }
+
     private void setSmtp() {
-        Map<String, String> smtp = new HashMap<>();
+        final Map<String, String> smtp = new HashMap<>();
         smtp.put("host", StringUtils.defaultIfBlank(_javaMailSender.getHost(), "localhost"));
         smtp.put("port", Integer.toString(_javaMailSender.getPort()));
         if (StringUtils.isNotBlank(_javaMailSender.getUsername())) {
@@ -593,7 +790,7 @@ public class NotificationsApi extends AbstractXapiRestController {
         _notificationsPrefs.setSmtpServer(smtp);
     }
 
-    private void cleanProperties(final Map<String, String> properties) {
+    private void cleanProperties(final Properties properties) {
         if (properties.containsKey("host")) {
             properties.remove("host");
         }
@@ -653,7 +850,7 @@ public class NotificationsApi extends AbstractXapiRestController {
         }
     }
 
-    private void logConfigurationSubmit(final String host, final int port, final String username, final String password, final String protocol, final Map<String, String> properties) {
+    private void logConfigurationSubmit(final String host, final int port, final String username, final String password, final String protocol, final Properties properties) {
         if (_log.isInfoEnabled()) {
             final StringBuilder message = new StringBuilder("User ");
             message.append(getSessionUser().getLogin()).append(" setting mail properties to:\n");
@@ -663,7 +860,7 @@ public class NotificationsApi extends AbstractXapiRestController {
             message.append(" * Username: ").append(StringUtils.equals(NOT_SET, username) ? "No value submitted..." : username).append("\n");
             message.append(" * Password: ").append(StringUtils.equals(NOT_SET, password) ? "No value submitted..." : "********").append("\n");
             if (properties != null && properties.size() > 0) {
-                for (final String property : properties.keySet()) {
+                for (final String property : properties.stringPropertyNames()) {
                     message.append(" * ").append(property).append(": ").append(properties.get(property)).append("\n");
                 }
             }
@@ -677,4 +874,5 @@ public class NotificationsApi extends AbstractXapiRestController {
     private final NotificationsPreferences _notificationsPrefs;
     private final JavaMailSenderImpl       _javaMailSender;
     private final XnatAppInfo              _appInfo;
+    private final SerializerService        _serializer;
 }
diff --git a/src/main/java/org/nrg/xnat/configuration/WebConfig.java b/src/main/java/org/nrg/xnat/configuration/WebConfig.java
index 2829c778..2d7c20dd 100644
--- a/src/main/java/org/nrg/xnat/configuration/WebConfig.java
+++ b/src/main/java/org/nrg/xnat/configuration/WebConfig.java
@@ -1,6 +1,7 @@
 package org.nrg.xnat.configuration;
 
 import org.nrg.framework.annotations.XapiRestController;
+import org.nrg.xnat.services.XnatAppInfo;
 import org.nrg.xnat.spawner.configuration.SpawnerConfig;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -22,10 +23,13 @@ import org.springframework.web.servlet.view.JstlView;
 import springfox.documentation.builders.PathSelectors;
 import springfox.documentation.builders.RequestHandlerSelectors;
 import springfox.documentation.service.ApiInfo;
+import springfox.documentation.service.Contact;
 import springfox.documentation.spi.DocumentationType;
 import springfox.documentation.spring.web.plugins.Docket;
 import springfox.documentation.swagger2.annotations.EnableSwagger2;
 
+import java.util.Locale;
+
 @Configuration
 @EnableWebMvc
 @EnableSwagger2
@@ -65,15 +69,25 @@ public class WebConfig extends WebMvcConfigurerAdapter {
     }
 
     @Bean
-    public Docket api() {
+    public Docket api(final XnatAppInfo info, final MessageSource messageSource) {
         _log.debug("Initializing the Swagger Docket object");
-        return new Docket(DocumentationType.SWAGGER_2).select().apis(RequestHandlerSelectors.withClassAnnotation(XapiRestController.class)).paths(PathSelectors.any()).build().apiInfo(apiInfo()).pathMapping("/xapi");
+        return new Docket(DocumentationType.SWAGGER_2).select().apis(RequestHandlerSelectors.withClassAnnotation(XapiRestController.class)).paths(PathSelectors.any()).build().apiInfo(apiInfo(info, messageSource)).pathMapping("/xapi");
+    }
+
+    private ApiInfo apiInfo(final XnatAppInfo info, final MessageSource messageSource) {
+        return new ApiInfo(getMessage(messageSource, "apiInfo.title"),
+                           getMessage(messageSource, "apiInfo.description"),
+                           info.getVersion(),
+                           getMessage(messageSource, "apiInfo.termsOfServiceUrl"),
+                           new Contact(getMessage(messageSource, "apiInfo.contactName"),
+                                       getMessage(messageSource, "apiInfo.contactUrl"),
+                                       getMessage(messageSource, "apiInfo.contactEmail")),
+                           getMessage(messageSource, "apiInfo.license"),
+                           getMessage(messageSource, "apiInfo.licenseUrl"));
     }
 
-    private ApiInfo apiInfo() {
-        // TODO: The deprecated class is ApiInfo(). There's no documentation on how to use the replacement Contact class instead, so migrate this when that's explained.
-        //noinspection deprecation
-        return new ApiInfo("XNAT REST API", "The XNAT REST API (XAPI) functions provide access to XNAT internal functions for remote clients.", "1.7.0", "http://www.xnat.org", "info@xnat.org", "Simplified 2-Clause BSD", "API license URL");
+    private String getMessage(final MessageSource messageSource, final String messageId) {
+        return messageSource.getMessage(messageId, null, Locale.getDefault());
     }
 
     private static final Logger _log = LoggerFactory.getLogger(WebConfig.class);
diff --git a/src/main/java/org/nrg/xnat/services/XnatAppInfo.java b/src/main/java/org/nrg/xnat/services/XnatAppInfo.java
index 827be957..45c6d15f 100644
--- a/src/main/java/org/nrg/xnat/services/XnatAppInfo.java
+++ b/src/main/java/org/nrg/xnat/services/XnatAppInfo.java
@@ -17,19 +17,19 @@ import java.util.jar.Manifest;
 @Component
 public class XnatAppInfo {
 
-    public static final int           MILLISECONDS_IN_A_DAY    = (24 * 60 * 60 * 1000);
-    public static final int           MILLISECONDS_IN_AN_HOUR  = (60 * 60 * 1000);
-    public static final int           MILLISECONDS_IN_A_MINUTE = (60 * 1000);
-    public static final DecimalFormat SECONDS_FORMAT           = new DecimalFormat("##.000");
-    public static final String        DAYS                     = "days";
-    public static final String        HOURS                    = "hours";
-    public static final String        MINUTES                  = "minutes";
-    public static final String        SECONDS                  = "seconds";
+    private static final int           MILLISECONDS_IN_A_DAY    = (24 * 60 * 60 * 1000);
+    private static final int           MILLISECONDS_IN_AN_HOUR  = (60 * 60 * 1000);
+    private static final int           MILLISECONDS_IN_A_MINUTE = (60 * 1000);
+    private static final DecimalFormat SECONDS_FORMAT           = new DecimalFormat("##.000");
+    private static final String        DAYS                     = "days";
+    private static final String        HOURS                    = "hours";
+    private static final String        MINUTES                  = "minutes";
+    private static final String        SECONDS                  = "seconds";
 
     @Inject
     public XnatAppInfo(final ServletContext context, final JdbcTemplate template) throws IOException {
         try (final InputStream input = context.getResourceAsStream("/META-INF/MANIFEST.MF")) {
-            final Manifest manifest = new Manifest(input);
+            final Manifest   manifest   = new Manifest(input);
             final Attributes attributes = manifest.getMainAttributes();
             _properties.setProperty("buildNumber", attributes.getValue("Build-Number"));
             _properties.setProperty("buildDate", attributes.getValue("Build-Date"));
@@ -84,14 +84,14 @@ public class XnatAppInfo {
     /**
      * Returns the primary XNAT system properties extracted from the installed application's manifest file. These
      * properties are guaranteed to include the following:
-     *
+     * <p>
      * <ul>
      * <li>version</li>
      * <li>buildNumber</li>
      * <li>buildDate</li>
      * <li>commit</li>
      * </ul>
-     *
+     * <p>
      * There may be other properties available in the system properties and even more available through the {@link
      * #getSystemAttributes()} method.
      *
@@ -101,6 +101,42 @@ public class XnatAppInfo {
         return (Properties) _properties.clone();
     }
 
+    /**
+     * Gets the version of the application.
+     *
+     * @return The version of the application.
+     */
+    public String getVersion() {
+        return _properties.getProperty("version");
+    }
+
+    /**
+     * Gets the build number of the application.
+     *
+     * @return The build number of the application.
+     */
+    public String getBuildNumber() {
+        return _properties.getProperty("buildNumber");
+    }
+
+    /**
+     * Gets the date the application was built.
+     *
+     * @return The date the application was built.
+     */
+    public String getBuildDate() {
+        return _properties.getProperty("buildDate");
+    }
+
+    /**
+     * Gets the commit number in the source repository from which the application was built.
+     *
+     * @return The commit number of the application.
+     */
+    public String getCommit() {
+        return _properties.getProperty("commit");
+    }
+
     /**
      * Returns extended XNAT system attributes.
      *
@@ -128,12 +164,12 @@ public class XnatAppInfo {
      * @return A map of values indicating the system uptime.
      */
     public Map<String, String> getUptime() {
-        final long diff = new Date().getTime() - _startTime.getTime();
-        final int days = (int) (diff / MILLISECONDS_IN_A_DAY);
-        final long daysRemainder = diff % MILLISECONDS_IN_A_DAY;
-        final int hours = (int) (daysRemainder / MILLISECONDS_IN_AN_HOUR);
-        final long hoursRemainder = daysRemainder % MILLISECONDS_IN_AN_HOUR;
-        final int minutes = (int) (hoursRemainder / MILLISECONDS_IN_A_MINUTE);
+        final long diff             = new Date().getTime() - _startTime.getTime();
+        final int  days             = (int) (diff / MILLISECONDS_IN_A_DAY);
+        final long daysRemainder    = diff % MILLISECONDS_IN_A_DAY;
+        final int  hours            = (int) (daysRemainder / MILLISECONDS_IN_AN_HOUR);
+        final long hoursRemainder   = daysRemainder % MILLISECONDS_IN_AN_HOUR;
+        final int  minutes          = (int) (hoursRemainder / MILLISECONDS_IN_A_MINUTE);
         final long minutesRemainder = hoursRemainder % MILLISECONDS_IN_A_MINUTE;
 
         final Map<String, String> uptime = new HashMap<>();
@@ -158,7 +194,7 @@ public class XnatAppInfo {
      */
     public String getFormattedUptime() {
         final Map<String, String> uptime = getUptime();
-        final StringBuilder buffer = new StringBuilder();
+        final StringBuilder       buffer = new StringBuilder();
         if (uptime.containsKey(DAYS)) {
             buffer.append(uptime.get(DAYS)).append(" days, ");
         }
diff --git a/src/main/resources/org/nrg/xnat/messages/system.properties b/src/main/resources/org/nrg/xnat/messages/system.properties
index ac767a1b..75204fb3 100644
--- a/src/main/resources/org/nrg/xnat/messages/system.properties
+++ b/src/main/resources/org/nrg/xnat/messages/system.properties
@@ -1 +1,10 @@
+apiInfo.title=XNAT REST API
+apiInfo.description=The XNAT REST API (XAPI) functions provide access to XNAT internal functions for remote clients.
+apiInfo.termsOfServiceUrl=http://www.xnat.org/download
+apiInfo.contactName=XNAT
+apiInfo.contactUrl=http://www.xnat.org
+apiInfo.contactEmail=info@xnat.org
+apiInfo.license=Simplified 2-Clause BSD
+apiInfo.licenseUrl=https://opensource.org/licenses/BSD-2-Clause
+
 providerManager.providerNotFound=No authentication provider found for {0}
\ No newline at end of file
-- 
GitLab