diff --git a/.gitignore b/.gitignore
index e9537f4d9069ad15b02dff54efd2538e696ea2ba..48510445ec153093bd764bb7000913be4c068b39 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,7 +6,8 @@ src/generated
 gradle.properties
 /out
 /build
-InstanceSettings.xml
+InstanceSettings.xmldependencies.txt
+/webapps
 /bin/
 .classpath
 .project
diff --git a/README.md b/README.md
index 907ad1cc00ca6fa2c0989475ce368b1ed9e30289..51d22e6d0d373937f8b85165e58b39dd7a2d3432 100644
--- a/README.md
+++ b/README.md
@@ -62,6 +62,24 @@ gradle clean jar publishToMavenLocal publishMavenJavaPublicationToMavenRepositor
 
 For this last one, the values set for **repoUsername** and **repoPassword** must be valid credentials for pushing artifacts to the Maven server.
 
+You can specify the name of the generated WAR file (and thus the application context of the application within the Tomcat server) from the command line or a properties file.
+
+On the command line, add the flag **-ParchiveName=name[.war]** to your Gradle command (.war will be appended if it’s not specified). This may look something like this:
+
+```bash
+./gradlew -ParchiveName=ROOT.war war
+```
+
+You can also set the **archiveName** value in the **gradle.properties** file. **gradle.properties** can be in your repository folder, thus affecting only the local build, or in **~/.gradle/gradle.properties**, which will affect any build that uses the **archiveName** property. To set this value in **gradle.properties**, just add the line:
+
+```properties
+archiveName=ROOT
+```
+
+If you don’t explicitly set **archiveName**, the build uses the naming scheme **xnat-web-_version_.war**.
+
+Note that **gradle.properties** is in this repository's **.gitignore** file, so if you create a local version you won’t get the annoying “Untracked files” message from git.
+
 # Configuring #
 
 You must perform a couple of configuration steps in your run-time environment (e.g. your local development workstation, a Vagrant VM, etc.) in order for XNAT to run properly:
@@ -74,6 +92,28 @@ You must perform a couple of configuration steps in your run-time environment (e
 
 ## From Gradle ##
 
+You can deploy your generated war file to a local Tomcat instance with the **deployToTomcat** task. Unlike the Cargo tasks described below, this task doesn't go through the Tomcat manager or transfer the war via network connection. Instead, it copies the physical war file to a local folder named **webapps**. The key is that you need to specify the location of your Tomcat instance with the property **tomcatHome**. As with **archiveName** above, you can specify this on the command line or in the **gradle.properties** file:
+
+```bash
+./gradlew -PtomcatHome=/var/lib/tomcat7
+```
+```properties
+tomcatHome=/var/lib/tomcat7
+```
+
+**tomcatHome** defaults to ‘.’, so if you don’t specify a value for it, you’ll end up with a folder named **webapps** in your local repository folder. That folder is also in **.gitignore** so you won’t get bugged by git. You probably didn't intend to have another copy of the war file somewhere in your development folder, but it’s better than copying files off to random locations or pitching a fit.
+
+Note that **deployToTomcat** depends on the **war** task, so there’s no need to specify the **war** task explicitly.
+
+This provides an efficient workflow for development on a VM or server: set the two values in **gradle.properties** and you can quickly redeploy:
+
+```bash
+./gradlew deployToTomcat
+```
+
+Gradle will compile or package any changes since the last build as necessary, re-package your application into a war if needed, and copy the resulting war file into the Tomcat **webapps** folder. It’s worth noting if nothing has changed the war won’t be regenerated or copied.
+
+
 You can deploy the built war to a remote Tomcat using the Cargo plugin.
 
 ```bash
diff --git a/build.gradle b/build.gradle
index f1525d77c9d0c0d94602b287ae300dd2e8125717..b4a5e48469a209a703e0bf7fa0c6d9efb094ec8a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -34,11 +34,24 @@ buildscript {
         jcenter()
     }
     dependencies {
+        classpath "org.nrg.xnat.build:gradle-xnat-plugin:1.7.0-SNAPSHOT"
         classpath "com.bmuschko:gradle-cargo-plugin:2.2.2"
         classpath "com.bmuschko:gradle-tomcat-plugin:2.2.4"
     }
 }
 
+apply plugin: 'xnat'
+apply plugin: 'groovy'
+apply plugin: 'java'
+apply plugin: 'war'
+apply plugin: 'maven'
+apply plugin: 'maven-publish'
+apply plugin: 'ivy-publish'
+apply plugin: 'com.bmuschko.tomcat'
+apply plugin: 'com.bmuschko.cargo'
+apply plugin: 'idea'
+apply plugin: 'eclipse'
+
 repositories {
     mavenLocal()
     maven {
@@ -61,23 +74,16 @@ repositories {
     jcenter()
 }
 
-apply plugin: 'groovy'
-apply plugin: 'java'
-apply plugin: 'war'
-apply plugin: 'maven'
-apply plugin: 'maven-publish'
-apply plugin: 'ivy-publish'
-apply plugin: 'com.bmuschko.tomcat'
-apply plugin: 'com.bmuschko.cargo'
-apply plugin: 'idea'
-apply plugin: 'eclipse'
-
 configurations {
     sourceCompatibility = 1.7
     targetCompatibility = 1.7
 }
 
+if (hasProperty("archiveName")) {
+    war.archiveName = archiveName.endsWith(".war") ? archiveName : "${archiveName}.war"
+} else {
 war.baseName = "xnat-web"
+}
 
 sourceSets {
     main {
@@ -98,6 +104,95 @@ tomcatRunWar.contextPath = '/'
     task.additionalRuntimeResources << file('/Users/rherrick/xnat/config/plugins')
 }
 
+task deployToTomcat(type: Copy, dependsOn: war) {
+    from war.archivePath
+    into "${propertyWithDefault('tomcatHome', '.')}/webapps"
+}
+
+cargo {
+    // These properties must be set in the ~/.gradle/gradle.properties file or passed on the Gradle command line in
+    // the form -PdeployPort=8080 -PdeployContext=/ -PdeployHost=localhost -PdeployUser=deploy -PdeployPassword=deploy
+    containerId = 'tomcat7x'
+    port = propertyWithDefault('deployPort', 8080) as int
+
+    deployable {
+        context = propertyWithDefault('deployContext', '/')
+    }
+
+    remote {
+        hostname = propertyWithDefault('deployHost', 'localhost')
+        username = propertyWithDefault('deployUser', 'deploy')
+        password = propertyWithDefault('deployPassword', 'deploy')
+    }
+}
+
+task sourceJar(type: Jar, dependsOn: classes) {
+    from sourceSets.main.allSource
+}
+
+task javadocJar(type: Jar, dependsOn: javadoc) {
+    from javadoc.destinationDir
+}
+
+publishing {
+    publications {
+        mavenJava(MavenPublication) {
+            from components.java
+
+            artifact sourceJar {
+                classifier "sources"
+            }
+
+            artifact javadocJar {
+                classifier "javadoc"
+            }
+
+            pom.withXml {
+                def root = asNode()
+                root.appendNode('name', 'XNAT Server')
+                root.appendNode('description', 'XNAT is an open-source imaging informatics software platform dedicated to helping you perform imaging-based research. XNAT’s core functions manage importing, archiving, processing and securely distributing imaging and related study data. But its extended uses continue to evolve.')
+                root.appendNode('url', 'https://bitbucket.org/xnatdev/xnat-web')
+                root.appendNode('inceptionYear', '2016')
+
+                def scm = root.appendNode('scm')
+                scm.appendNode('url', 'https://bitbucket.org/xnatdev/xnat-web')
+                scm.appendNode('connection', 'scm:https://bitbucket.org/xnatdev/xnat-web.git')
+                scm.appendNode('developerConnection', 'scm:git://bitbucket.org/xnatdev/xnat-web.git')
+
+                def license = root.appendNode('licenses').appendNode('license')
+                license.appendNode('name', 'Simplified BSD 2-Clause License')
+                license.appendNode('url', 'http://xnat.org/about/license.php')
+                license.appendNode('distribution', 'repo')
+
+                def developers = root.appendNode('developers')
+                def rherrick = developers.appendNode('developer')
+                rherrick.appendNode('id', 'rherrick')
+                rherrick.appendNode('name', 'Rick Herrick')
+                rherrick.appendNode('email', 'jrherrick@wustl.edu')
+            }
+        }
+    }
+    repositories {
+        maven {
+            credentials {
+                // These properties must be set in the ~/.gradle/gradle.properties file or passed on the Gradle command
+                // line in the form -PrepoUsername=foo -PrepoPassword=bar.
+                username propertyWithDefault('repoUsername', 'username')
+                password propertyWithDefault('repoPassword', 'password')
+            }
+            if (project.version.endsWith('-SNAPSHOT')) {
+                url "https://nrgxnat.artifactoryonline.com/nrgxnat/libs-snapshot-local"
+            } else {
+                url "https://nrgxnat.artifactoryonline.com/nrgxnat/libs-release-local"
+            }
+        }
+    }
+}
+
+def propertyWithDefault(def String property, def Object value) {
+    hasProperty(property) ? getProperty(property) : value
+}
+
 configurations {
     all*.exclude group: 'avalon-framework'
     all*.exclude group: 'avalon-logkit'
@@ -142,6 +237,7 @@ configurations {
 
 dependencies {
     // NRG dependencies first.
+    compile "org.nrg.xnat:spawner:${vXnat}"
     compile "org.nrg.xdat:core:${vXnat}"
     compile "org.nrg:framework:${vXnat}"
     compile "org.nrg:transaction:${vXnat}"
@@ -221,6 +317,7 @@ dependencies {
     compile "com.fasterxml.jackson.core:jackson-annotations:${vJackson}"
     compile "com.fasterxml.jackson.core:jackson-core:${vJackson}"
     compile "com.fasterxml.jackson.core:jackson-databind:${vJackson}"
+    compile "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:${vJackson}"
     compile "org.json:json:20151123"
     compile "xerces:xercesImpl:2.11.0"
 
@@ -292,84 +389,3 @@ dependencies {
     testCompile "org.springframework:spring-test:${vSpring}"
 }
 
-cargo {
-    // These properties must be set in the ~/.gradle/gradle.properties file or passed on the Gradle command line in
-    // the form -PdeployPort=8080 -PdeployContext=/ -PdeployHost=localhost -PdeployUser=deploy -PdeployPassword=deploy
-    containerId = 'tomcat7x'
-    port = propertyWithDefault('deployPort', 8080) as int
-
-    deployable {
-        context = propertyWithDefault('deployContext', '/')
-    }
-
-    remote {
-        hostname = propertyWithDefault('deployHost', 'localhost')
-        username = propertyWithDefault('deployUser', 'deploy')
-        password = propertyWithDefault('deployPassword', 'deploy')
-    }
-}
-
-publishing {
-    publications {
-        mavenJava(MavenPublication) {
-            from components.java
-
-            pom.withXml {
-                def root = asNode()
-                root.appendNode('name', 'XNAT Server')
-                root.appendNode('description', 'XNAT is an open-source imaging informatics software platform dedicated to helping you perform imaging-based research. XNAT’s core functions manage importing, archiving, processing and securely distributing imaging and related study data. But its extended uses continue to evolve.')
-                root.appendNode('url', 'https://bitbucket.org/xnatdev/xnat-web')
-                root.appendNode('inceptionYear', '2016')
-
-                def scm = root.appendNode('scm')
-                scm.appendNode('url', 'https://bitbucket.org/xnatdev/xnat-web')
-                scm.appendNode('connection', 'scm:https://bitbucket.org/xnatdev/xnat-web.git')
-                scm.appendNode('developerConnection', 'scm:git://bitbucket.org/xnatdev/xnat-web.git')
-
-                def license = root.appendNode('licenses').appendNode('license')
-                license.appendNode('name', 'Simplified BSD 2-Clause License')
-                license.appendNode('url', 'http://xnat.org/about/license.php')
-                license.appendNode('distribution', 'repo')
-
-                def developers = root.appendNode('developers')
-                def rherrick = developers.appendNode('developer')
-                rherrick.appendNode('id', 'rherrick')
-                rherrick.appendNode('name', 'Rick Herrick')
-                rherrick.appendNode('email', 'jrherrick@wustl.edu')
-            }
-        }
-    }
-    repositories {
-        def blankProperty = "xxx"
-        maven {
-            credentials {
-                // These properties must be set in the ~/.gradle/gradle.properties file or passed on the Gradle command
-                // line in the form -PrepoUsername=foo -PrepoPassword=bar.
-                username propertyWithDefault('repoUsername', 'username')
-                password propertyWithDefault('repoPassword', 'password')
-            }
-            if (project.version.endsWith('-SNAPSHOT')) {
-                url "https://nrgxnat.artifactoryonline.com/nrgxnat/libs-snapshot-local"
-            } else {
-                url "https://nrgxnat.artifactoryonline.com/nrgxnat/libs-release-local"
-            }
-        }
-        ivy {
-            credentials {
-                // These properties must be set in the ~/.gradle/gradle.properties file or passed on the Gradle command
-                // line in the form -DrepoUsername=foo -DrepoPassword=bar.
-                username propertyWithDefault('repoUsername', 'username')
-                password propertyWithDefault('repoPassword', 'password')
-            }
-            if (project.version.endsWith('-SNAPSHOT')) {
-                url "https://nrgxnat.artifactoryonline.com/nrgxnat/libs-snapshot-local"
-            } else {
-                url "https://nrgxnat.artifactoryonline.com/nrgxnat/libs-release-local"
-            }
-        }
-    }
-}
-
-def propertyWithDefault(def String property, def Object value) {
-    hasProperty(property) ? getProperty(property) : value
-}
diff --git a/src/main/java/org/nrg/dcm/preferences/DicomSCPInstance.java b/src/main/java/org/nrg/dcm/preferences/DicomSCPInstance.java
index 096f3d7f3d433b152e9c384ef70be27668f64f81..0d3a43d1bf1dce89d5f7e47d982375f09c4e53d3 100644
--- a/src/main/java/org/nrg/dcm/preferences/DicomSCPInstance.java
+++ b/src/main/java/org/nrg/dcm/preferences/DicomSCPInstance.java
@@ -1,11 +1,8 @@
 package org.nrg.dcm.preferences;
 
 import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-import java.io.IOException;
 
+@SuppressWarnings("WeakerAccess")
 @JsonInclude(JsonInclude.Include.NON_DEFAULT)
 public class DicomSCPInstance {
     @SuppressWarnings("unused")
@@ -29,14 +26,6 @@ public class DicomSCPInstance {
         setFileNamer(fileNamer);
     }
 
-    public static DicomSCPInstance deserialize(final String json) throws IOException {
-        return _mapper.readValue(json, DicomSCPInstance.class);
-    }
-
-    public static String serialize(final DicomSCPInstance instance) throws IOException {
-        return _mapper.writeValueAsString(instance);
-    }
-
     public String getScpId() {
         return _scpId;
     }
@@ -97,11 +86,6 @@ public class DicomSCPInstance {
                 '}';
     }
 
-    private static final ObjectMapper _mapper = new ObjectMapper();
-    static {
-        _mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
-    }
-
     private String _scpId;
     private int _port;
     private String _aeTitle;
diff --git a/src/main/java/org/nrg/xapi/rest/AbstractXnatRestApi.java b/src/main/java/org/nrg/xapi/rest/AbstractXnatRestApi.java
deleted file mode 100644
index 1c2e5618ccf9f84c1fe3c419d581adfa64122139..0000000000000000000000000000000000000000
--- a/src/main/java/org/nrg/xapi/rest/AbstractXnatRestApi.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package org.nrg.xapi.rest;
-
-import org.nrg.xdat.security.XDATUser;
-import org.nrg.xft.security.UserI;
-import org.springframework.http.HttpStatus;
-import org.springframework.security.core.context.SecurityContextHolder;
-
-/**
- * Provides basic functions for integrating Spring REST controllers with XNAT.
- */
-public abstract class AbstractXnatRestApi {
-    protected HttpStatus isPermitted(String id) {
-        UserI sessionUser = getSessionUser();
-        if (sessionUser == null) {
-            return HttpStatus.UNAUTHORIZED;
-        }
-        if ((sessionUser.getUsername().equals(id)) || (isPermitted() == null)) {
-            return null;
-        }
-        return HttpStatus.FORBIDDEN;
-    }
-
-    protected UserI getSessionUser() {
-        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
-        if ((principal instanceof UserI)) {
-            return (UserI) principal;
-        }
-        return null;
-    }
-
-    protected HttpStatus isPermitted() {
-        UserI sessionUser = getSessionUser();
-        if ((sessionUser instanceof XDATUser)) {
-            return ((XDATUser) sessionUser).isSiteAdmin() ? null : HttpStatus.FORBIDDEN;
-        }
-
-        return null;
-    }
-}
diff --git a/src/main/java/org/nrg/xapi/rest/dicomscp/DicomSCPApi.java b/src/main/java/org/nrg/xapi/rest/dicomscp/DicomSCPApi.java
index 6ef05d7cabeb205819782043b12951ba85dc92dd..60c151817753a7403dbae71850d3a3025110de2e 100644
--- a/src/main/java/org/nrg/xapi/rest/dicomscp/DicomSCPApi.java
+++ b/src/main/java/org/nrg/xapi/rest/dicomscp/DicomSCPApi.java
@@ -5,6 +5,7 @@ import org.nrg.dcm.DicomSCPManager;
 import org.nrg.dcm.preferences.DicomSCPInstance;
 import org.nrg.framework.exceptions.NrgServiceException;
 import org.nrg.xapi.rest.NotFoundException;
+import org.nrg.xdat.rest.AbstractXnatRestApi;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.http.HttpStatus;
@@ -19,7 +20,7 @@ import java.util.List;
 @Api(description = "XNAT DICOM SCP management API")
 @RestController
 @RequestMapping(value = "/dicomscp")
-public class DicomSCPApi extends org.nrg.xapi.rest.AbstractXnatRestApi {
+public class DicomSCPApi extends AbstractXnatRestApi {
     private static final Logger _log = LoggerFactory.getLogger(DicomSCPApi.class);
 
     @ApiOperation(value = "Get list of all configured DICOM SCP receiver definitions.", notes = "The primary DICOM SCP retrieval function returns a list of all DICOM SCP receivers defined for the current system.", response = DicomSCPInstance.class, responseContainer = "List")
@@ -148,8 +149,8 @@ public class DicomSCPApi extends org.nrg.xapi.rest.AbstractXnatRestApi {
         return new ResponseEntity<>(_manager.stopDicomSCPs(), HttpStatus.OK);
     }
 
-    @ApiOperation(value = "Stops the DICOM SCP receiver.", notes = "This stops the DICOM SCP receiver. Note that this will stop the receiver regardless of its enabled or disabled setting. This returns true if the instance was stoped and false if not.", response = Boolean.class)
-    @ApiResponses({@ApiResponse(code = 200, message = "DICOM SCP receiver successfully stoped."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to enable or disable this DICOM SCP receiver definition."), @ApiResponse(code = 404, message = "DICOM SCP receiver definition not found."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @ApiOperation(value = "Stops the DICOM SCP receiver.", notes = "This stops the DICOM SCP receiver. Note that this will stop the receiver regardless of its enabled or disabled setting. This returns true if the instance was stopped and false if not.", response = Boolean.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "DICOM SCP receiver successfully stopped."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to enable or disable this DICOM SCP receiver definition."), @ApiResponse(code = 404, message = "DICOM SCP receiver definition not found."), @ApiResponse(code = 500, message = "Unexpected error")})
     @RequestMapping(value = {"/{id}/stop"}, produces = {"application/json"}, method = {RequestMethod.PUT})
     public ResponseEntity<Boolean> dicomSCPInstanceStop(@ApiParam(value = "ID of the DICOM SCP receiver to stop.", required = true) @PathVariable("id") final String id) {
         HttpStatus status = isPermitted(id);
diff --git a/src/main/java/org/nrg/xapi/rest/theme/ThemeApi.java b/src/main/java/org/nrg/xapi/rest/theme/ThemeApi.java
new file mode 100644
index 0000000000000000000000000000000000000000..480d3f4730317449b5d069cc79041bc3c3329332
--- /dev/null
+++ b/src/main/java/org/nrg/xapi/rest/theme/ThemeApi.java
@@ -0,0 +1,170 @@
+/*
+ * org.nrg.xnat.turbine.modules.screens.ManageProtocol
+ * XNAT http://www.xnat.org
+ * Copyright (c) 2013, Washington University School of Medicine
+ * All Rights Reserved
+ *
+ * Released under the Simplified BSD.
+ *
+ * Author: Justin Cleveland <clevelandj@wustl.edu>
+ * Last modified 3/7/2016 11:52 AM
+ */
+
+package org.nrg.xapi.rest.theme;
+
+import io.swagger.annotations.*;
+import org.apache.commons.io.FileUtils;
+import org.nrg.xapi.rest.NotFoundException;
+import org.nrg.xdat.security.XDATUser;
+import org.nrg.xft.security.UserI;
+import org.nrg.xnat.configuration.ThemeConfig;
+import org.nrg.xnat.services.ThemeService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+@Api(description = "XNAT Theme Management API")
+@RestController
+@RequestMapping(value = "/theme")
+public class ThemeApi {
+    private static final Logger _log = LoggerFactory.getLogger(ThemeApi.class);
+
+    @Autowired
+    private ThemeService themeService;
+
+    @ApiOperation(value = "Get the currently selected global theme or a role based theme if specified.", notes = "Use this to get the theme selected by the system administrator on the Theme Management page.", response = ThemeConfig.class, responseContainer = "ThemeConfig")
+    @ApiResponses({@ApiResponse(code = 200, message = "Reports the currently selected global theme (if there is one) and whether or not it's enabled."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @RequestMapping(value = {"/{role}"}, produces = {"application/json", "application/xml"}, method = RequestMethod.GET)
+    public ResponseEntity<ThemeConfig> themeGet(@ApiParam(value = "\"global\" or role name of currently set theme", required = true) @PathVariable("role") String role) {
+        if("global".equalsIgnoreCase(role)){
+            return new ResponseEntity<>(themeService.getTheme(), HttpStatus.OK);
+        }
+        return new ResponseEntity<>(themeService.getTheme(role), HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "Get list of available themes.", notes = "Use this to get a list of all available themes on the XNAT system.", response = ThemeService.TypeOption.class, responseContainer = "List")
+    @ApiResponses({@ApiResponse(code = 200, message = "Reports the currently selected global theme (if there is one), whether or not it's enabled, and a list of available themes on the system in a JSON string."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @RequestMapping(produces = {"application/json", "application/xml"}, method = RequestMethod.GET)
+    public ResponseEntity<List<ThemeService.TypeOption>> themesGet() {
+        return new ResponseEntity<>(themeService.loadExistingThemes(), HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "Deletes the theme with the specified name.", notes = "Returns success on deletion. ", response = String.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Theme was successfully deleted."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to delete a theme."), @ApiResponse(code = 404, message = "Theme not found."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @RequestMapping(value = {"/{theme}"}, produces = {"application/json", "application/xml", "text/html"}, method = {RequestMethod.DELETE})
+    public ResponseEntity<ThemeConfig> themeDelete(@ApiParam(value = "Name of the theme to delete", required = true) @PathVariable("theme") String theme) {
+        ThemeConfig themeConfig = null;
+        HttpStatus status = isPermitted();
+        if (status != null) {
+            return new ResponseEntity<>(status);
+        }
+        if("null".equals(theme)){
+            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+        }
+        if(!themeService.themeExists(theme)) {
+            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+        }
+        try {
+            File f = new File(themeService.getThemesPath() + File.separator + theme);
+            FileUtils.deleteDirectory(f);
+            if(!f.exists()) {
+                themeConfig = themeService.getTheme();
+                String themeName = (themeConfig != null) ? themeConfig.getName() : null;
+                if (theme.equals(themeName)) {
+                    themeService.setTheme((ThemeConfig) null);
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
+        }
+        return themeConfig != null ? new ResponseEntity<>(themeConfig, HttpStatus.OK) : new ResponseEntity<ThemeConfig>(HttpStatus.NOT_FOUND);
+    }
+
+    @ApiOperation(value = "Sets the current global theme to the one specified.", notes = "Returns the updated serialized theme object.", response = ThemeConfig.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Successfully updated the current global theme."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to create or update this user."), @ApiResponse(code = 404, message = "Theme not found."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @RequestMapping(value = {"/{theme}"}, produces = {"application/json", "application/xml", "text/html"}, method = {RequestMethod.PUT})
+    public ResponseEntity<ThemeConfig> themePut(@ApiParam(value = "The name of the theme to select.", required = true) @PathVariable("theme") String theme, @RequestParam(value = "enabled", required=false, defaultValue="true") String enabled) throws NotFoundException {
+        HttpStatus status = isPermitted();
+        if (status != null) {
+            return new ResponseEntity<>(status);
+        }
+        ThemeConfig themeConfig;
+        try {
+            boolean themeEnabled = true;
+            if("false".equalsIgnoreCase(enabled)){
+                themeEnabled = false;
+            }
+            if("null".equals(theme)){
+                theme = null;
+            }
+            themeConfig = themeService.setTheme(theme, themeEnabled);
+        } catch (ThemeService.ThemeNotFoundException e) {
+            _log.error(e.getInvalidTheme()+" not found.");
+            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+        } catch (Exception e) {
+            _log.error("An error occurred setting the theme " + theme, e);
+            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
+        }
+        return new ResponseEntity<>(themeConfig, HttpStatus.OK);                        // TODO: fix the return on this. It's showing up as [object Object] on the page!
+    }
+
+    @ApiOperation(value = "Accepts a multipart form with a zip file upload and extracts its contents in the theme system folder. If successful, the first (root) directory name (or theme name) unzipped is returned in the response. This will overwrite any other directories already existing with the same name without warning.", notes = "The structure of the zipped package must have only directories at it's root.", response = String.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Theme package successfully uploaded and extracted."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to upload a theme package."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @RequestMapping(produces = {"application/json"}, method = {RequestMethod.POST})
+    public ResponseEntity<List<ThemeService.TypeOption>> themePostUpload(@ApiParam(value = "Multipart file object being uploaded", required = true) @RequestParam(value="themePackage", required=false) MultipartFile themePackage) {
+        HttpStatus status = isPermitted();
+        if (status != null) {
+            return new ResponseEntity<>(status);
+        }
+        List<ThemeService.TypeOption> themeOptions = new ArrayList<>();
+        try {
+            if(!themePackage.getContentType().contains("zip")) {
+                String error = "No valid files were uploaded. Theme package must be of type: application/x-zip-compressed";
+                _log.error(error);
+                return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
+            }
+            List<String> dirs = themeService.extractTheme(themePackage.getInputStream());
+            for (String dir : dirs) {
+                themeOptions.add(new ThemeService.TypeOption(dir, dir));
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+            String error = "An error occurred extracting the theme package";
+            _log.error(error, e);
+            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
+        } catch (Exception e) {
+            e.printStackTrace();
+            String error = "An unknown error occurred accepting the theme package";
+            _log.error(error, e);
+            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
+        }
+        return new ResponseEntity<>(themeOptions, HttpStatus.OK);
+    }
+
+    private UserI getSessionUser() {
+        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
+        if ((principal instanceof UserI)) {
+            return (UserI) principal;
+        }
+        return null;
+    }
+
+    private HttpStatus isPermitted() {
+        UserI sessionUser = getSessionUser();
+        if ((sessionUser instanceof XDATUser)) {
+            return ((XDATUser) sessionUser).isSiteAdmin() ? null : HttpStatus.FORBIDDEN;
+        }
+        return null;
+    }
+}
diff --git a/src/main/java/org/nrg/xapi/rest/users/UsersApi.java b/src/main/java/org/nrg/xapi/rest/users/UsersApi.java
index 34fe4503d523444757a45266d1664b4310bfe703..83adf8bec14a3763714b4df255e46c030cd60d9d 100644
--- a/src/main/java/org/nrg/xapi/rest/users/UsersApi.java
+++ b/src/main/java/org/nrg/xapi/rest/users/UsersApi.java
@@ -3,9 +3,8 @@ package org.nrg.xapi.rest.users;
 import io.swagger.annotations.*;
 import org.apache.commons.lang3.StringUtils;
 import org.nrg.xapi.model.users.User;
-import org.nrg.xapi.rest.AbstractXnatRestApi;
 import org.nrg.xapi.rest.NotFoundException;
-import org.nrg.xdat.security.XDATUser;
+import org.nrg.xdat.rest.AbstractXnatRestApi;
 import org.nrg.xdat.security.helpers.Users;
 import org.nrg.xdat.security.user.exceptions.UserInitException;
 import org.nrg.xdat.security.user.exceptions.UserNotFoundException;
@@ -16,7 +15,6 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
-import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.ArrayList;
diff --git a/src/main/java/org/nrg/xnat/archive/GradualDicomImporter.java b/src/main/java/org/nrg/xnat/archive/GradualDicomImporter.java
index 621f883a34f2ff80bd632ebd65149ee55f76d3f4..f1cfb0c0b6d09fd6e197383bd36db89c6fdb7691 100644
--- a/src/main/java/org/nrg/xnat/archive/GradualDicomImporter.java
+++ b/src/main/java/org/nrg/xnat/archive/GradualDicomImporter.java
@@ -361,6 +361,7 @@ public class GradualDicomImporter extends ImporterHandlerA {
             //
             // This record is necessary so that, if this row was created by this call, it can be deleted if anonymization
             // goes wrong. In case of any other error the file is left on the filesystem.
+            // TODO: This is where things are going awry with Jenny's prearchive bug.
             Either<SessionData, SessionData> getOrCreate;
             try {
                 getOrCreate = PrearcDatabase.eitherGetOrCreateSession(session, tsdir, shouldAutoArchive(project, dicom));
diff --git a/src/main/java/org/nrg/xnat/configuration/SerializerConfig.java b/src/main/java/org/nrg/xnat/configuration/SerializerConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..f4702c9e57e2c3e2f71ac2d2ffc3ab16919d54f8
--- /dev/null
+++ b/src/main/java/org/nrg/xnat/configuration/SerializerConfig.java
@@ -0,0 +1,47 @@
+package org.nrg.xnat.configuration;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.PrettyPrinter;
+import com.fasterxml.jackson.core.util.DefaultIndenter;
+import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+import org.jetbrains.annotations.NotNull;
+import org.nrg.xnat.utils.SerializerService;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class SerializerConfig {
+
+    @Bean
+    @NotNull
+    public PrettyPrinter prettyPrinter() {
+        final DefaultIndenter      indenter = new DefaultIndenter("    ", DefaultIndenter.SYS_LF);
+        final DefaultPrettyPrinter printer  = new DefaultPrettyPrinter();
+        printer.indentObjectsWith(indenter);
+        printer.indentArraysWith(indenter);
+        return printer;
+    }
+
+    @Bean
+    public ObjectMapper jsonObjectMapper() {
+        final PrettyPrinter printer = prettyPrinter();
+        final ObjectMapper  mapper  = new ObjectMapper().setDefaultPrettyPrinter(printer);
+        mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
+        return mapper;
+    }
+
+    @Bean
+    public ObjectMapper yamlObjectMapper() {
+        final PrettyPrinter printer = prettyPrinter();
+        final ObjectMapper  mapper  = new ObjectMapper(new YAMLFactory()).setDefaultPrettyPrinter(printer);
+        mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
+        return mapper;
+    }
+
+    @Bean
+    public SerializerService serializerService() {
+        return new SerializerService();
+    }
+}
diff --git a/src/main/java/org/nrg/xnat/configuration/SpawnerConfig.java b/src/main/java/org/nrg/xnat/configuration/SpawnerConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..93f2d7afcef20a7354af68cf67573b9e31e5d66c
--- /dev/null
+++ b/src/main/java/org/nrg/xnat/configuration/SpawnerConfig.java
@@ -0,0 +1,30 @@
+package org.nrg.xnat.configuration;
+
+import org.nrg.framework.orm.hibernate.HibernateEntityPackageList;
+import org.nrg.xnat.spawner.services.SpawnerResourceLocator;
+import org.nrg.xnat.spawner.services.impl.SpawnerWorker;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.Collections;
+
+@Configuration
+@ComponentScan({"org.nrg.xnat.spawner.controllers", "org.nrg.xnat.spawner.services.impl.hibernate", "org.nrg.xnat.spawner.repositories"})
+public class SpawnerConfig {
+    @Bean
+    public SpawnerWorker spawnerWorker() {
+        return new SpawnerWorker();
+    }
+
+    @Bean
+    public HibernateEntityPackageList spawnerEntityPackages() {
+        return new HibernateEntityPackageList(Collections.singletonList("org.nrg.xnat.spawner.entities"));
+    }
+
+    @Bean
+    public SpawnerResourceLocator spawnerResourceLocator() {
+        // TODO: This uses the default spawner element pattern. It would be nice to set this as a site configuration property.
+        return new SpawnerResourceLocator();
+    }
+}
diff --git a/src/main/java/org/nrg/xnat/configuration/ThemeConfig.java b/src/main/java/org/nrg/xnat/configuration/ThemeConfig.java
index f0337e268af035f1c599b72d867c55398ae0d454..ca054e8cd1c35cd3dde953c0ddb2d50f01d222e2 100644
--- a/src/main/java/org/nrg/xnat/configuration/ThemeConfig.java
+++ b/src/main/java/org/nrg/xnat/configuration/ThemeConfig.java
@@ -28,9 +28,6 @@ public class ThemeConfig {
      */
     public ThemeConfig() {
     }
-    public ThemeConfig(String themeName) {
-        this.name = themeName;
-    }
     public ThemeConfig(String themeName, String themePath, boolean enabled) {
         this.name = themeName;
         this.path = themePath;
diff --git a/src/main/java/org/nrg/xapi/configuration/WebConfig.java b/src/main/java/org/nrg/xnat/configuration/WebConfig.java
similarity index 98%
rename from src/main/java/org/nrg/xapi/configuration/WebConfig.java
rename to src/main/java/org/nrg/xnat/configuration/WebConfig.java
index 5cb9a6989c694de29c966293602aed6406451a7e..d2e4900f9fbd4e5d5f09df57334ebeae401cd398 100644
--- a/src/main/java/org/nrg/xapi/configuration/WebConfig.java
+++ b/src/main/java/org/nrg/xnat/configuration/WebConfig.java
@@ -1,4 +1,4 @@
-package org.nrg.xapi.configuration;
+package org.nrg.xnat.configuration;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
diff --git a/src/main/java/org/nrg/xnat/event/listeners/PipelineEmailHandlerAbst.java b/src/main/java/org/nrg/xnat/event/listeners/PipelineEmailHandlerAbst.java
index b9fc9febc1639b56d477bd5726fe434a89b53d03..f62842042a01eb3421d14f38cac51f6da980fc1f 100644
--- a/src/main/java/org/nrg/xnat/event/listeners/PipelineEmailHandlerAbst.java
+++ b/src/main/java/org/nrg/xnat/event/listeners/PipelineEmailHandlerAbst.java
@@ -1,15 +1,14 @@
 package org.nrg.xnat.event.listeners;
 
 import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import org.apache.commons.lang.StringEscapeUtils;
 import org.apache.commons.lang.StringUtils;
-import org.apache.log4j.Logger;
 import org.apache.xmlbeans.XmlException;
 import org.nrg.pipeline.xmlbeans.AllResolvedStepsDocument;
 import org.nrg.pipeline.xmlbeans.ParameterData;
+import org.nrg.xdat.XDAT;
 import org.nrg.xdat.model.WrkXnatexecutionenvironmentParameterI;
 import org.nrg.xdat.om.WrkWorkflowdata;
 import org.nrg.xdat.om.WrkXnatexecutionenvironment;
@@ -20,6 +19,9 @@ import org.nrg.xdat.turbine.utils.TurbineUtils;
 import org.nrg.xft.db.PoolDBUtils;
 import org.nrg.xft.event.WorkflowStatusEvent;
 import org.nrg.xnat.notifications.NotifyProjectPipelineListeners;
+import org.nrg.xnat.utils.SerializerService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.File;
 import java.io.IOException;
@@ -27,26 +29,25 @@ import java.nio.charset.Charset;
 import java.nio.file.Files;
 import java.util.*;
 
-/**
- * Created by flavin on 2/27/15.
- */
+@SuppressWarnings("WeakerAccess")
 public abstract class PipelineEmailHandlerAbst extends WorkflowStatusEventHandlerAbst {
     
-    /** The logger. */
-    static Logger logger = Logger.getLogger(PipelineEmailHandlerAbst.class);
-
     /** The default template success. */
     public final String DEFAULT_TEMPLATE_SUCCESS = "/screens/PipelineEmail_success.vm";
-    
+
     /** The default subject success. */
     public final String DEFAULT_SUBJECT_SUCCESS = "processed without errors";
-    
+
     /** The default template failure. */
     public final String DEFAULT_TEMPLATE_FAILURE = "/screens/PipelineEmail_failure.vm";
-    
+
     /** The default subject failure. */
     public final String DEFAULT_SUBJECT_FAILURE = "";
 
+    protected PipelineEmailHandlerAbst() {
+        _serializer = XDAT.getContextService().getBean(SerializerService.class);
+    }
+
     /**
      * Send.
      *
@@ -93,14 +94,14 @@ public abstract class PipelineEmailHandlerAbst extends WorkflowStatusEventHandle
                 }
 
                 if (completed(e) && Float.parseFloat(wrk.getPercentagecomplete()) < 100.0f) {
-                    logger.error("Workflow "+wrk.getWrkWorkflowdataId()+" is \"Complete\" but percentage is less than 100%. Not sending email.");
+                    _log.error("Workflow " + wrk.getWrkWorkflowdataId() + " is \"Complete\" but percentage is less than 100%. Not sending email.");
                     return;
                 }
                 SchemaElement objXsiType;
                 try {
                     objXsiType = SchemaElement.GetElement(wrk.getDataType());
                 } catch (Throwable e1) {
-                    logger.error("", e1);//this shouldn't happen
+                    _log.error("", e1);//this shouldn't happen
                     return;
                 }
 
@@ -131,24 +132,15 @@ public abstract class PipelineEmailHandlerAbst extends WorkflowStatusEventHandle
                     throw new Exception("Experiment " + wrk.getId() + " associated with workflow " + wrk.getWrkWorkflowdataId() + " is null");
                 }
 
-
-
                 if (wrk.getComments()!=null) {
                     String comments = StringEscapeUtils.unescapeXml(wrk.getComments());
 
                     if (StringUtils.isNotBlank(comments)) {
-                        ObjectMapper objectMapper = new ObjectMapper();
-                        HashMap<String, String> commentsMap = null;
                         try {
-                            commentsMap = objectMapper.readValue(comments, new TypeReference<HashMap<String, String>>(){});
+                            params.putAll(getSerializer().deserializeJson(comments, new TypeReference<HashMap<String, String>>(){}));
                         } catch (Exception e1) {
                             // Do nothing. This isn't necessarily a problem.
-                        }
-
-                        if (commentsMap == null) {
                             params.put("comments",comments);
-                        } else {
-                            params.putAll(commentsMap);
                         }
                     }
                 }
@@ -156,7 +148,6 @@ public abstract class PipelineEmailHandlerAbst extends WorkflowStatusEventHandle
                 if(failed(e)) {
                     // Get a list of parameters. Using that information, find the pipeline execution logs.
                     WrkXnatexecutionenvironment wrkEE=null;
-                    List<WrkXnatexecutionenvironmentParameterI> pipelineCmdLineParameters = null;
                     HashMap<String,String> pipelineCmdLineParamsMap = Maps.newHashMap();
                     HashMap<String,String> pipelineParamsMap = Maps.newHashMap();
                     Map<String,File> attachments = Maps.newHashMap();
@@ -165,9 +156,9 @@ public abstract class PipelineEmailHandlerAbst extends WorkflowStatusEventHandle
                     try {
                         wrkEE = (WrkXnatexecutionenvironment) wrk.getExecutionenvironment();
                     } catch (ClassCastException e1){
-                        logger.error("Workflow Execution Environment is not an XNAT Execution Environment",e1);
+                        _log.error("Workflow Execution Environment is not an XNAT Execution Environment", e1);
                     }
-                    pipelineCmdLineParameters = null==wrkEE ? new ArrayList<WrkXnatexecutionenvironmentParameterI>() : wrkEE.getParameters_parameter();
+                    final List<WrkXnatexecutionenvironmentParameterI> pipelineCmdLineParameters = null == wrkEE ? new ArrayList<WrkXnatexecutionenvironmentParameterI>() : wrkEE.getParameters_parameter();
 
                     // Gather input params from command line
                     for (WrkXnatexecutionenvironmentParameterI pipelineParameter : pipelineCmdLineParameters) {
@@ -193,14 +184,14 @@ public abstract class PipelineEmailHandlerAbst extends WorkflowStatusEventHandle
                                                 stderr = logFileContents;
                                             }
                                         } catch (IOException e1) {
-                                            logger.error("Could not read pipeline log file "+logFileObj.toPath(), e1);
+                                            _log.error("Could not read pipeline log file " + logFileObj.toPath(), e1);
                                         }
                                     } else if (logFileObj.getName().endsWith(".xml")) {
                                         AllResolvedStepsDocument pipeParamsDoc = null;
                                         try {
                                             pipeParamsDoc = AllResolvedStepsDocument.Factory.parse(logFileObj);
                                         } catch (XmlException | IOException e1) {
-                                            logger.error("Encountered a problem parsing pipeline parameter XML for failure email.",e1);
+                                            _log.error("Encountered a problem parsing pipeline parameter XML for failure email.", e1);
                                         }
 
                                         if (null!=pipeParamsDoc) {
@@ -262,7 +253,7 @@ public abstract class PipelineEmailHandlerAbst extends WorkflowStatusEventHandle
 
             }
         } catch (Throwable e1) {
-            logger.error("",e1);
+            _log.error("", e1);
         }
     }
 
@@ -283,5 +274,13 @@ public abstract class PipelineEmailHandlerAbst extends WorkflowStatusEventHandle
         }
         return retList;
     }
+
+    protected SerializerService getSerializer() {
+        return _serializer;
+    }
+
+    private final static Logger _log = LoggerFactory.getLogger(PipelineEmailHandlerAbst.class);
+
+    private final SerializerService _serializer;
 }
 
diff --git a/src/main/java/org/nrg/xnat/helpers/prearchive/PrearcDatabase.java b/src/main/java/org/nrg/xnat/helpers/prearchive/PrearcDatabase.java
index 0cbc305b2f96a0c5230b934c7966c1695ccc0559..29c48d481823db1c27a021daddde0bab95e19cc0 100644
--- a/src/main/java/org/nrg/xnat/helpers/prearchive/PrearcDatabase.java
+++ b/src/main/java/org/nrg/xnat/helpers/prearchive/PrearcDatabase.java
@@ -11,7 +11,6 @@
 package org.nrg.xnat.helpers.prearchive;
 
 import com.fasterxml.jackson.core.JsonFactory;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.common.base.Strings;
 import com.google.common.collect.Maps;
 import org.apache.commons.collections.CollectionUtils;
@@ -42,6 +41,7 @@ import org.nrg.xnat.restlet.XNATApplication;
 import org.nrg.xnat.restlet.actions.PrearcImporterA.PrearcSession;
 import org.nrg.xnat.restlet.services.Archiver;
 import org.nrg.xnat.turbine.utils.ArcSpecManager;
+import org.nrg.xnat.utils.SerializerService;
 import org.nrg.xnat.utils.XnatUserProvider;
 import org.restlet.data.Status;
 import org.slf4j.Logger;
@@ -61,7 +61,6 @@ import java.util.*;
 
 public final class PrearcDatabase {
     private static final Logger logger = LoggerFactory.getLogger(PrearcTableBuilder.class);
-    public static final ObjectMapper MAPPER = new ObjectMapper(new JsonFactory());
     public static Connection conn;
     final static String table = "prearchive";
     final static String tableWithSchema = PoolDBUtils.search_schema_name + "." + PrearcDatabase.table;
@@ -73,6 +72,8 @@ public final class PrearcDatabase {
 
     private static String prearcPath;
 
+    private static SerializerService _serializer;
+
     public static final String SPLIT_PETMR_SESSION_ID = "SplitPetMrSessions";
 
     public static final String DEFAULT_SPLIT_PETMR_SESSION_FILTER = "{\n" +
@@ -713,10 +714,17 @@ public final class PrearcDatabase {
         } else {
             content = script.getContent();
         }
-        final LinkedHashMap<String, String> keys = MAPPER.readValue(content, SeriesImportFilter.MAP_TYPE_REFERENCE);
+        final LinkedHashMap<String, String> keys = getSerializer().deserializeJson(content, SeriesImportFilter.MAP_TYPE_REFERENCE);
         return DicomFilterService.buildSeriesImportFilter(keys);
     }
 
+    private static SerializerService getSerializer() {
+        if (_serializer == null) {
+            _serializer = XDAT.getContextService().getBean(SerializerService.class);
+        }
+        return _serializer;
+    }
+
     static String getUniqueSessionLabel(final String stem, final String target, final String replacement, final String projectId, final UserI user) {
         String label;
         if (stem.contains(target.toUpperCase())) {
diff --git a/src/main/java/org/nrg/xnat/helpers/resource/XnatResourceInfo.java b/src/main/java/org/nrg/xnat/helpers/resource/XnatResourceInfo.java
index 61859fad5c881c572226bf6b7c8a5971185acd8b..4ab354ad91294ac87337e8f556d90b9c84e07d82 100644
--- a/src/main/java/org/nrg/xnat/helpers/resource/XnatResourceInfo.java
+++ b/src/main/java/org/nrg/xnat/helpers/resource/XnatResourceInfo.java
@@ -10,8 +10,8 @@
  */
 package org.nrg.xnat.helpers.resource;
 
+import org.apache.commons.lang3.StringUtils;
 import org.nrg.xft.security.UserI;
-import org.nrg.xft.utils.StringUtils;
 
 import java.io.Serializable;
 import java.util.*;
@@ -20,8 +20,8 @@ public class XnatResourceInfo implements Serializable {
     private static final long serialVersionUID = 42L;
 	private String description,format,content=null;
 	private Number event_id=null;
-	private List<String> tags=new ArrayList<String>();
-	private Map<String,String> meta=new HashMap<String,String>();
+	private List<String> tags= new ArrayList<>();
+	private Map<String,String> meta= new HashMap<>();
 	private final Date lastModified,created;
 	private final UserI user;
 	
@@ -100,14 +100,14 @@ public class XnatResourceInfo implements Serializable {
 	    if(tags!=null){
 	    	for(String tag: tags){
 	    		tag = tag.trim();
-	    		if(!tag.equals("")){
-	    			for(String s:StringUtils.CommaDelimitedStringToArrayList(tag)){
+	    		if(StringUtils.isNotBlank(tag)){
+	    			for(String s: tag.split("[\\s]*,[\\s]*")){
 	    				s=s.trim();
 	    				if(!s.equals("")){
-	    		    		if(s.indexOf("=")>-1){
+	    		    		if(s.contains("=")){
 	    		    			info.addMeta(s.substring(0,s.indexOf("=")),s.substring(s.indexOf("=")+1));
 	    		    		}else{
-	    		    			if(s.indexOf(":")>-1){
+	    		    			if(s.contains(":")){
 		    		    			info.addMeta(s.substring(0,s.indexOf(":")),s.substring(s.indexOf(":")+1));
 		    		    		}else{
 		    		    			info.addTag(s);
diff --git a/src/main/java/org/nrg/xnat/initialization/XnatWebAppInitializer.java b/src/main/java/org/nrg/xnat/initialization/XnatWebAppInitializer.java
index 55ca5b3e349e21300aa2e6c15056b69d28ec8518..3c4f7fcdcff0763c666d222a2bcab18d6d9ebe31 100644
--- a/src/main/java/org/nrg/xnat/initialization/XnatWebAppInitializer.java
+++ b/src/main/java/org/nrg/xnat/initialization/XnatWebAppInitializer.java
@@ -7,6 +7,7 @@ import org.apache.commons.lang3.StringUtils;
 import org.apache.turbine.Turbine;
 import org.nrg.framework.exceptions.NrgServiceRuntimeException;
 import org.nrg.framework.processors.XnatPluginBean;
+import org.nrg.framework.utilities.BasicXnatResourceLocator;
 import org.nrg.xdat.servlet.XDATAjaxServlet;
 import org.nrg.xdat.servlet.XDATServlet;
 import org.nrg.xnat.restlet.servlet.XNATRestletServlet;
@@ -15,7 +16,6 @@ import org.nrg.xnat.security.XnatSessionEventPublisher;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.core.io.Resource;
-import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
 import org.springframework.core.io.support.PropertiesLoaderUtils;
 import org.springframework.web.filter.DelegatingFilterProxy;
 import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
@@ -114,9 +114,7 @@ public class XnatWebAppInitializer extends AbstractAnnotationConfigDispatcherSer
     private List<Class<?>> getPluginConfigs() {
         final List<Class<?>> configs = new ArrayList<>();
         try {
-            final PathMatchingResourcePatternResolver resolver  = new PathMatchingResourcePatternResolver();
-            final Resource[]                          resources = resolver.getResources("classpath*:META-INF/xnat/**/*-plugin.properties");
-            for (final Resource resource : resources) {
+            for (final Resource resource : BasicXnatResourceLocator.getResources("classpath*:META-INF/xnat/**/*-plugin.properties")) {
                 final Properties     properties = PropertiesLoaderUtils.loadProperties(resource);
                 final XnatPluginBean plugin     = new XnatPluginBean(properties);
                 final Class<?>       config     = plugin.getConfigClass();
diff --git a/src/main/java/org/nrg/xnat/restlet/actions/PullSessionDataFromHeaders.java b/src/main/java/org/nrg/xnat/restlet/actions/PullSessionDataFromHeaders.java
index 48f3fa2bc4d3d74815b059df0e882da6ed3c980c..e736ecc874684d4b2c2f6e2179de23430d2b55b4 100644
--- a/src/main/java/org/nrg/xnat/restlet/actions/PullSessionDataFromHeaders.java
+++ b/src/main/java/org/nrg/xnat/restlet/actions/PullSessionDataFromHeaders.java
@@ -15,6 +15,7 @@ import java.io.IOException;
 import java.util.Calendar;
 import java.util.concurrent.Callable;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 import org.nrg.xdat.base.BaseElement;
 import org.nrg.xdat.model.XnatAbstractresourceI;
@@ -30,7 +31,7 @@ import org.nrg.xft.event.EventMetaI;
 import org.nrg.xft.schema.Wrappers.XMLWrapper.SAXReader;
 import org.nrg.xft.security.UserI;
 import org.nrg.xft.utils.SaveItemHelper;
-import org.nrg.xft.utils.StringUtils;
+import org.nrg.xft.utils.XftStringUtils;
 import org.nrg.xft.utils.ValidationUtils.ValidationResultsI;
 import org.nrg.xnat.archive.XNATSessionBuilder;
 import org.nrg.xnat.exceptions.ValidationException;
@@ -66,7 +67,6 @@ public class PullSessionDataFromHeaders implements Callable<Boolean> {
 	 * 
 	 * @throws IOException
 	 * @throws SAXException
-	 * @throws MultipleSessionException
 	 * @throws ValidationException: Scan invalid according to schema requirements (including xdat tags)
 	 * @throws Exception
 	 */
@@ -129,11 +129,11 @@ public class PullSessionDataFromHeaders implements Callable<Boolean> {
 
 							final XnatAbstractresourceI oldCat=oldScan.getFile().get(0);
 							if(oldCat instanceof XnatResource){
-								if(StringUtils.IsEmpty(((XnatResource)oldCat).getContent()) && !StringUtils.IsEmpty(newcat.getContent()))
+								if(StringUtils.isBlank(((XnatResource)oldCat).getContent()) && StringUtils.isNotBlank(newcat.getContent()))
 									((XnatResource)oldCat).setContent(newcat.getContent());
-								if(StringUtils.IsEmpty(((XnatResource)oldCat).getFormat()) && !StringUtils.IsEmpty(newcat.getFormat()))
+								if(StringUtils.isBlank(((XnatResource)oldCat).getFormat()) && StringUtils.isNotBlank(newcat.getFormat()))
 									((XnatResource)oldCat).setFormat(newcat.getFormat());
-								if(StringUtils.IsEmpty(((XnatResource)oldCat).getDescription()) && !StringUtils.IsEmpty(newcat.getDescription()))
+								if(StringUtils.isBlank(((XnatResource)oldCat).getDescription()) && StringUtils.isNotBlank(newcat.getDescription()))
 									((XnatResource)oldCat).setDescription(newcat.getDescription());
 							}
 
diff --git a/src/main/java/org/nrg/xnat/restlet/extensions/DicomSCPRestlet.java b/src/main/java/org/nrg/xnat/restlet/extensions/DicomSCPRestlet.java
index 56617d3280cd269620881c0bc8bb3914b4f61906..03949ce06f1ad400bc72421110cb4507e8c6e79f 100644
--- a/src/main/java/org/nrg/xnat/restlet/extensions/DicomSCPRestlet.java
+++ b/src/main/java/org/nrg/xnat/restlet/extensions/DicomSCPRestlet.java
@@ -11,7 +11,6 @@
 
 package org.nrg.xnat.restlet.extensions;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.common.base.Joiner;
 import org.apache.commons.lang3.StringUtils;
 import org.nrg.dcm.DicomSCPManager;
@@ -42,10 +41,9 @@ public class DicomSCPRestlet extends SecureResource {
     private static final List<String> ALLOWED_ACTIONS = new ArrayList<>(Arrays.asList("status", "start", "stop", "enable", "disable"));
     private static final Logger       _log            = LoggerFactory.getLogger(DicomSCPRestlet.class);
 
-    private static final ObjectMapper _mapper = new ObjectMapper();
-    private final DicomSCPManager _dicomSCPManager;
-    private final String          _scpId;
-    private final String          _action;
+    private final DicomSCPManager   _dicomSCPManager;
+    private final String            _scpId;
+    private final String            _action;
 
     public DicomSCPRestlet(Context context, Request request, Response response) throws ResourceException {
         super(context, request, response);
@@ -86,11 +84,11 @@ public class DicomSCPRestlet extends SecureResource {
 
         try {
             if (Method.DELETE.equals(getRequest().getMethod()) || (StringUtils.isNotBlank(_action) && _action.equalsIgnoreCase("status"))) {
-                return new StringRepresentation(_mapper.writeValueAsString(_dicomSCPManager.areDicomSCPsStarted()));
+                return new StringRepresentation(getSerializer().toJson(_dicomSCPManager.areDicomSCPsStarted()));
             } else if (StringUtils.isBlank(_scpId)) {
-                return new StringRepresentation(_mapper.writeValueAsString(_dicomSCPManager.getDicomSCPInstances()));
+                return new StringRepresentation(getSerializer().toJson(_dicomSCPManager.getDicomSCPInstances()));
             } else {
-                return new StringRepresentation(DicomSCPInstance.serialize(_dicomSCPManager.getDicomSCPInstance(_scpId)));
+                return new StringRepresentation(getSerializer().toJson(_dicomSCPManager.getDicomSCPInstance(_scpId)));
             }
         } catch (IOException e) {
             throw new ResourceException(Status.SERVER_ERROR_INTERNAL, "An error occurred marshaling the DICOM SCP statuses", e);
@@ -110,8 +108,8 @@ public class DicomSCPRestlet extends SecureResource {
             getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "You should only POST to this URL to create a new DICOM SCP instance. The DICOM SCP ID " + _scpId + " is invalid in this context.");
         }
         try {
-            final String serialized = getRequest().getEntity().getText();
-            final DicomSCPInstance instance = DicomSCPInstance.deserialize(serialized);
+            final String           serialized = getRequest().getEntity().getText();
+            final DicomSCPInstance instance   = getSerializer().deserializeJson(serialized, DicomSCPInstance.class);
             _dicomSCPManager.create(instance);
         } catch (IOException e) {
             getResponse().setStatus(Status.SERVER_ERROR_INTERNAL, e, "An error occurred trying to retrieve the body text of the PUT request.");
diff --git a/src/main/java/org/nrg/xnat/restlet/extensions/PipelineDetailsRestlet.java b/src/main/java/org/nrg/xnat/restlet/extensions/PipelineDetailsRestlet.java
index 30118abd40e2820e295d66db9f1bec63167c4098..117eb76a178e0ad5db3717ecae704b808e157dc7 100644
--- a/src/main/java/org/nrg/xnat/restlet/extensions/PipelineDetailsRestlet.java
+++ b/src/main/java/org/nrg/xnat/restlet/extensions/PipelineDetailsRestlet.java
@@ -10,7 +10,6 @@
  */
 package org.nrg.xnat.restlet.extensions;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.apache.commons.lang.StringUtils;
 import org.nrg.pipeline.PipelineRepositoryManager;
 import org.nrg.pipeline.utils.PipelineFileUtils;
@@ -43,15 +42,13 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-
-
 @XnatRestlet({"/projects/{PROJECT_ID}/pipelines/{PIPELINE_NAME}", "/projects/{PROJECT_ID}/pipelines/{PIPELINE_NAME}/details"})
 public class PipelineDetailsRestlet extends SecureResource {
     public static final String PARAM_PROJECT_ID = "PROJECT_ID";
     public static final String PARAM_PIPELINE_NAME = "PIPELINE_NAME";
 
-    private final String _projectId;
-    private final String _pipelineName;
+    private final String            _projectId;
+    private final String            _pipelineName;
 
     private static final Logger _log = LoggerFactory.getLogger(PipelineDetailsRestlet.class);
 
@@ -79,8 +76,7 @@ public class PipelineDetailsRestlet extends SecureResource {
             Map<String,Object> pipelineDetails = getPipelineDetailsMap();
 
             // Make a json object from the pipelineDetails map
-            ObjectMapper mapper = new ObjectMapper();
-            String json = mapper.writeValueAsString(pipelineDetails);
+            String json = getSerializer().toJson(pipelineDetails);
             return new StringRepresentation(json, MediaType.APPLICATION_JSON);
 
         } catch (Exception exception) {
@@ -119,7 +115,7 @@ public class PipelineDetailsRestlet extends SecureResource {
         }
 
         // Build hash map
-        Map<String,Object> pipelineDetails = new HashMap<String,Object>();
+        Map<String,Object> pipelineDetails = new HashMap<>();
 
         // Basic info
         pipelineDetails.put("path",pipelineDescriptorPath);
@@ -155,9 +151,9 @@ public class PipelineDetailsRestlet extends SecureResource {
                 pipelineDetails.put("publications",doc.getPublications().getPublicationArray());
             }
             if (doc.isSetAuthors()) {
-                List<Map<String,String>> authorInfoList = new ArrayList<Map<String,String>>();
+                List<Map<String,String>> authorInfoList = new ArrayList<>();
                 for (Author aAuthor : doc.getAuthors().getAuthorArray()) {
-                    Map<String,String> authorInfo = new HashMap<String,String>();
+                    Map<String,String> authorInfo = new HashMap<>();
                     if (StringUtils.isNotBlank(aAuthor.getFirstname())) {
                         authorInfo.put("firstname", aAuthor.getFirstname());
                     }
@@ -183,9 +179,9 @@ public class PipelineDetailsRestlet extends SecureResource {
         }
 
         // Step ids and descriptions
-        List<Map<String,String>> stepInfoList = new ArrayList<Map<String,String>>();
+        List<Map<String,String>> stepInfoList = new ArrayList<>();
         for (Step aStep : pipelineData.getSteps().getStepArray()) {
-            Map<String,String> stepInfo = new HashMap<String,String>();
+            Map<String,String> stepInfo = new HashMap<>();
             stepInfo.put("id",aStep.getId());
 
             if (StringUtils.isNotBlank(aStep.getDescription())) {
@@ -196,11 +192,11 @@ public class PipelineDetailsRestlet extends SecureResource {
         pipelineDetails.put("steps",stepInfoList);
 
         // Project-level param defaults
-        List<Map<String,Object>> paramInfoList = new ArrayList<Map<String,Object>>();
+        List<Map<String,Object>> paramInfoList = new ArrayList<>();
         for (ArcPipelineparameterdataI aParamI : projectPipelineData.getParameters_parameter()) {
             ArcPipelineparameterdata aParam = (ArcPipelineparameterdata) aParamI;
 
-            Map<String,Object> paramInfo = new HashMap<String,Object>();
+            Map<String,Object> paramInfo = new HashMap<>();
             paramInfo.put("name",aParam.getName());
             if (StringUtils.isNotBlank(aParam.getDescription())) {
                 paramInfo.put("description", aParam.getDescription());
@@ -209,7 +205,7 @@ public class PipelineDetailsRestlet extends SecureResource {
             String csv = aParam.getCsvvalues();
             String schemaLink = aParam.getSchemalink();
             if (StringUtils.isNotBlank(schemaLink) || StringUtils.isNotBlank(csv)) {
-                Map<String,String> paramValues = new HashMap<String,String>();
+                Map<String,String> paramValues = new HashMap<>();
                 if (StringUtils.isNotBlank(schemaLink)) {
                     paramValues.put("schemaLink",schemaLink);
                 } else {
@@ -223,5 +219,4 @@ public class PipelineDetailsRestlet extends SecureResource {
 
         return pipelineDetails;
     }
-
 }
diff --git a/src/main/java/org/nrg/xnat/restlet/extensions/ThemeRestlet.java b/src/main/java/org/nrg/xnat/restlet/extensions/ThemeRestlet.java
deleted file mode 100644
index d072e0c0e17b9e6e64335db6262ea84dd07119ac..0000000000000000000000000000000000000000
--- a/src/main/java/org/nrg/xnat/restlet/extensions/ThemeRestlet.java
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * org.nrg.xnat.turbine.modules.screens.ManageProtocol
- * XNAT http://www.xnat.org
- * Copyright (c) 2013, Washington University School of Medicine
- * All Rights Reserved
- *
- * Released under the Simplified BSD.
- *
- * Author: Justin Cleveland <clevelandj@wustl.edu>
- * Last modified 1/25/2016 1:52 PM
- */
-
-package org.nrg.xnat.restlet.extensions;
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import org.apache.commons.fileupload.*;
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.nrg.action.ClientException;
-import org.nrg.xdat.security.helpers.Roles;
-import org.nrg.xnat.configuration.ThemeConfig;
-import org.nrg.xnat.restlet.XnatRestlet;
-import org.nrg.xnat.restlet.resources.SecureResource;
-import org.nrg.xnat.restlet.util.FileWriterWrapperI;
-import org.restlet.Context;
-import org.restlet.data.MediaType;
-import org.restlet.data.Request;
-import org.restlet.data.Response;
-import org.restlet.data.Status;
-import org.restlet.resource.Representation;
-import org.restlet.resource.StringRepresentation;
-import org.restlet.resource.Variant;
-
-import java.io.*;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-
-/**
- * Created by jcleve01 on 1/26/2016.
- */
-@XnatRestlet({"/theme"})
-public class ThemeRestlet extends SecureResource {
-    private static final Log _log = LogFactory.getLog(ThemeRestlet.class);
-    private static String themesPath;
-    protected final ObjectMapper mapper = new ObjectMapper();
-    private static final int FILE_BUFFER_SIZE = 4096;
-    private File themeFile = null;
-
-    public ThemeRestlet(Context context, Request request, Response response) throws UnsupportedEncodingException {
-        super(context, request, response);
-        setModifiable(true);
-        getVariants().add(new Variant(MediaType.APPLICATION_JSON));
-        themesPath = this.getHttpSession().getServletContext().getRealPath(File.separator)+"themes";
-        themeFile = new File(themesPath + File.separator + "theme.json");
-        File checkThemesPath = new File(themesPath);
-        if (!checkThemesPath.exists()) {
-            checkThemesPath.mkdir();
-        }
-    }
-
-    @Override
-    public boolean allowDelete() {
-        return true;
-    }
-
-    /** // TODO: convert to swagger doc tags
-     * Administrator use only
-     * Deletes the entire theme package folder specified by the "theme" query parameter. If it happens to be the
-     * currently applied theme, the current theme is set to null and the default XNAT theme will be restored.
-     */
-    @Override
-    public void handleDelete() {
-        String theme = this.getQueryVariable("theme");
-        if(theme != null && !theme.isEmpty()) {
-            File f = new File(themesPath + File.separator + theme);
-            if (Roles.isSiteAdmin(user)) {
-                try {
-                    FileUtils.deleteDirectory(f);
-                    ThemeConfig tc = getTheme();
-                    String themeName = (tc!=null)?tc.getName():null;
-                    if(theme != null && theme.equals(themeName)){
-                        setTheme(null);
-                    }
-                } catch (Exception e) {
-                    e.printStackTrace();
-                    getResponse().setStatus(Status.SERVER_ERROR_INTERNAL, "Theme directory deletion failed.");
-                }
-            } else {
-                getResponse().setStatus(Status.CLIENT_ERROR_UNAUTHORIZED, "Only site administrators can delete themes.");
-            }
-        }
-    }
-
-    @Override
-    public boolean allowPut() {
-        return true;
-    }
-
-    /** // TODO: convert to swagger doc tags
-     * Administrator use only
-     * Sets the currently selected global theme configuration package by updating the theme.json file under the
-     * application's theme folder and caches a copy of it in the app server's application context for reference by any
-     * other pages or servlets that may need to alter their styles and behaviors to fit the overriding theme.
-     */
-    @Override
-    public void handlePut() {
-        if (Roles.isSiteAdmin(user)) {
-            boolean enabled = true;
-            String name = this.getQueryVariable("theme"), path = themesPath + File.separator + name;
-            if("true".equals(this.getQueryVariable("disabled"))){
-                enabled = false;
-            }
-            setTheme(new ThemeConfig(name, themesPath, enabled));
-        } else {
-            getResponse().setStatus(Status.CLIENT_ERROR_UNAUTHORIZED, "Only site administrators can set the site theme.");
-        }
-    }
-
-    @Override
-    public boolean allowPost() {
-        return true;
-    }
-
-    /** // TODO: convert to swagger doc tags
-     * Administrator use only
-     * Accepts a multipart form with a zip file upload and extracts its contents in the theme system folder.
-     * The structure of the zipped package must have only directories at it's root
-     * If successful, the first (root) directory name (or theme name) unzipped is returned in the response.
-     * This will overwrite any other directories already existing with the same name without warning.
-     */
-    @Override
-    public void handlePost() { //Representation entity) { // Upload Theme package
-        Representation result = null;
-        if (Roles.isSiteAdmin(user)) {
-            try {
-                String firstDirName = "";
-                FileWriterWrapperI fw;
-                List<FileWriterWrapperI> fws = this.getFileWriters();
-                if (fws.size() == 0) {
-                    this.getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
-                    getResponse().setEntity("No valid files were uploaded.", MediaType.TEXT_PLAIN);
-                    return;
-                }
-                if (fws.size() > 1) {
-                    this.getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
-                    getResponse().setEntity("Theme importer is limited to one uploaded theme package per request.", MediaType.TEXT_PLAIN);
-                    return;
-                }
-                fw = fws.get(0);                  //TODO: inspect this fw object a little more for metadata
-                // TODO: validate zipped media type (fail otherwise)
-                final InputStream is = fw.getInputStream();
-                ZipInputStream zipIn = new ZipInputStream(is);
-                ZipEntry entry = zipIn.getNextEntry();
-                int dirCount = 0;
-                while (entry != null) {  // iterate over entries in the zip file
-                    String filePath = themesPath + File.separator + entry.getName();
-                    if (!entry.isDirectory()) {  // if the entry is a file, extract it      // TODO: Make sure we get a directory the first iteration through (fail otherwise) so that no files get dumped in the root themes directory
-                        extractFile(zipIn, filePath);
-                    } else {  // if the entry is a directory, make the directory
-                        if(dirCount == 0) {
-                            firstDirName = entry.getName();
-                            int slashIndex = firstDirName.indexOf('/');
-                            if(slashIndex>1){
-                                firstDirName = firstDirName.substring(0, slashIndex);
-                            }
-                        }
-                        dirCount++;
-                        File dir = new File(filePath);
-                        dir.mkdir();
-                    }
-                    zipIn.closeEntry();
-                    entry = zipIn.getNextEntry();
-                }
-                zipIn.close();
-                is.close();
-                getResponse().setEntity(firstDirName, MediaType.TEXT_PLAIN);
-            } catch (FileUploadBase.InvalidContentTypeException icte){
-                this.getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
-                getResponse().setEntity("Invalid Content Type: "+icte.getMessage(), MediaType.TEXT_PLAIN);
-            } catch (FileUploadException fue) {
-                this.getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
-                getResponse().setEntity("File Upload Exception: " + fue.getMessage(), MediaType.TEXT_PLAIN);
-            } catch (ClientException e) {
-                e.printStackTrace();
-            } catch (IOException e) {
-                e.printStackTrace();
-            }
-        } else {
-            getResponse().setStatus(Status.CLIENT_ERROR_UNAUTHORIZED, "Only site administrators can upload new themes.");
-        }
-    }
-
-    /**
-     * Extracts a zip entry (file entry)
-     * @param zip
-     * @param path
-     * @throws IOException
-     */
-    private void extractFile(ZipInputStream zip, String path) throws IOException {
-        BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(path));
-        byte[] bytes = new byte[FILE_BUFFER_SIZE];
-        int length = 0;
-        while ((length = zip.read(bytes)) != -1) {
-            os.write(bytes, 0, length);
-        }
-        os.close();
-    }
-
-    /** // TODO: convert to swagger doc tags
-     * Reports the currently selected global theme (if there is one), whether or not it's enabled, and a list of
-     * available themes on the system in a JSON string.
-     */
-    @Override
-    public Representation represent(Variant variant){
-        String themeOptions = null, themeName = null;
-        boolean themeEnabled = false;
-        ThemeConfig theme = getTheme();
-        try {
-            themeOptions = mapper.writeValueAsString(loadExistingThemes().toArray());
-        } catch (JsonProcessingException e) {
-            e.printStackTrace();
-        }
-        if(theme != null) {
-            themeName="\""+theme.getName()+"\"";
-            themeEnabled=theme.isEnabled();
-        }
-        return new StringRepresentation(
-                "{\"theme\":"+themeName+", \"enabled\":"+themeEnabled+", \"themeOptions\":"+themeOptions+"}",
-                MediaType.APPLICATION_JSON);
-    }
-
-    /**
-     * Loads the system theme options
-     * @return The list of the available theme packages (folder names) available under the system themes directory
-     */
-    public ArrayList<TypeOption> loadExistingThemes() {
-        ArrayList<TypeOption> themeOptions = new ArrayList<>();
-        themeOptions.add(new TypeOption(null, "None"));
-        File f = new File(themesPath); // current directory
-        FileFilter directoryFilter = new FileFilter() {
-            public boolean accept(File file) {
-                return file.isDirectory();
-            }
-        };
-        File[] files = f.listFiles(directoryFilter);
-        if(files != null) {
-            for (File file : files) {
-                if (file.isDirectory()) {
-                    themeOptions.add(new TypeOption(file.getName(), file.getName()));
-                }
-            }
-        }
-        return themeOptions;
-    }
-
-    /**
-     * Gets the currently selected system theme from an application servlet context cache, or secondarily from the
-     * theme.json file in the themes folder.
-     * @return The currently selected system theme configuration
-     */
-    public ThemeConfig getTheme() {
-        ThemeConfig themeConfig = null, cachedTheme = (ThemeConfig) this.getHttpSession().getServletContext().getAttribute("theme");
-        if(cachedTheme != null){
-            return cachedTheme;
-        } else {                        // Read the last saved theme selection from the theme.json file in the themes
-            if (themeFile.exists()) {   // directory in the event it can't be found in the application context.
-                try {                   // (ie. the server was just started/restarted)
-                    BufferedReader reader = new BufferedReader(new FileReader(themeFile));
-                    StringBuilder sb = new StringBuilder();
-                    String line;
-                    while ((line = reader.readLine()) != null) {
-                        sb.append(line).append("\n");
-                    }
-                    reader.close();
-                    String contents = sb.toString();
-                    themeConfig = mapper.readValue(contents, ThemeConfig.class);
-                } catch (IOException e) {
-                    e.printStackTrace();
-                }
-            }
-            setTheme(themeConfig);
-        }
-        return themeConfig;
-    }
-
-    /**
-     * Sets the currently selected system theme in the theme.json file in the web application's themes folder and
-     * stores it in the application servlet context cache
-     * @param themeConfig the theme configuration object to apply
-     */
-    public void setTheme(ThemeConfig themeConfig) {
-        try {
-            if (themeConfig != null) {
-                String themeJson = mapper.writeValueAsString(themeConfig);
-                if (!themeFile.exists()) {
-                    themeFile.createNewFile();
-                }
-                FileWriter writer = new FileWriter(themeFile);
-                writer.write(themeJson);
-                writer.flush();
-                writer.close();
-            } else {
-                themeFile.delete();
-                if (themeFile.exists()) {                           // Backup hack in case the file deletion does not
-                    FileWriter writer = new FileWriter(themeFile);  // succeed due to a file lock which sometimes
-                    writer.write("{\"name\":null}");                // strangely happens in Windows
-                    writer.flush();                                 // ...in that case wipe it out.
-                    writer.close();
-                }
-            }
-        } catch (JsonProcessingException e) {
-            e.printStackTrace();
-            // TODO: rethrow this and respond as an internal server error
-        } catch (IOException e) {
-            e.printStackTrace();
-        }
-        this.getHttpSession().getServletContext().setAttribute("theme", themeConfig);
-    }
-
-    /**
-     * Helper class to organize the available themes for display in a select dropdown form
-     */
-    public static class TypeOption implements Comparable<TypeOption> {
-        String value, label;
-        private TypeOption(String value, String label) {
-            this.value = value;
-            this.label = label;
-        }
-        public String getValue() {
-            return value;
-        }
-        public String getLabel() {
-            return label;
-        }
-        @Override
-        public int compareTo(TypeOption that) {
-            return this.label.compareToIgnoreCase(that.label);
-        }
-    }
-}
diff --git a/src/main/java/org/nrg/xnat/restlet/extensions/UserSettingsRestlet.java b/src/main/java/org/nrg/xnat/restlet/extensions/UserSettingsRestlet.java
index 84f4b3bf53c99907be68953de22b708aca9716f0..c0e4474383c9b4cebea78fe556b7585afe51669d 100644
--- a/src/main/java/org/nrg/xnat/restlet/extensions/UserSettingsRestlet.java
+++ b/src/main/java/org/nrg/xnat/restlet/extensions/UserSettingsRestlet.java
@@ -12,7 +12,6 @@ package org.nrg.xnat.restlet.extensions;
 
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.apache.commons.lang.StringUtils;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
@@ -49,8 +48,8 @@ import java.util.*;
 
 @XnatRestlet({"/user", "/user/{USER_ID}", "/user/actions/{ACTION}", "/user/actions/{USER_ID}/{ACTION}"})
 public class UserSettingsRestlet extends SecureResource {
-    public static final String PARAM_USER_ID = "USER_ID";
-    public static final String PARAM_ACTION = "ACTION";
+    private static final String PARAM_USER_ID = "USER_ID";
+    private static final String PARAM_ACTION  = "ACTION";
 
     public UserSettingsRestlet(Context context, Request request, Response response) throws ResourceException {
         super(context, request, response);
@@ -114,7 +113,7 @@ public class UserSettingsRestlet extends SecureResource {
         try {
             if (StringUtils.isBlank(_userId)) {
                 Collection<String> logins = Users.getAllLogins();
-                return new StringRepresentation(_isJsonRequested ? _serializer.writeValueAsString(logins) : createLoginListXml(logins));
+                return new StringRepresentation(_isJsonRequested ? getSerializer().toJson(logins) : createLoginListXml(logins));
             }
 
             UserI requestedUser = Users.getUser(_userId);
@@ -343,7 +342,7 @@ public class UserSettingsRestlet extends SecureResource {
 
     private JsonNode getJsonNode() throws IOException {
         if (_node == null) {
-            _node = _serializer.readValue(_payload, JsonNode.class);
+            _node = getSerializer().deserializeJson(_payload);
         }
         return _node;
     }
@@ -411,7 +410,7 @@ public class UserSettingsRestlet extends SecureResource {
         return new StringRepresentation(output.toString());
     }
 
-    enum UserAction {
+    private enum UserAction {
         Reset,
         ResetEmailRequests;
 
@@ -437,7 +436,7 @@ public class UserSettingsRestlet extends SecureResource {
         private static Map<String, UserAction> _actions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
     }
 
-    enum UserProperty {
+    private enum UserProperty {
         email,
         enabled,
         firstname,
@@ -458,11 +457,6 @@ public class UserSettingsRestlet extends SecureResource {
     }
 
     private static final Log _log = LogFactory.getLog(UserSettingsRestlet.class);
-    private static final ObjectMapper _serializer = new ObjectMapper() {{
-        // Migration: Get the mix-in annotations working for XDATUser.
-        // getSerializationConfig().addMixInAnnotations(UserI.class, IgnoreSetValueMixIn.class);
-        // getSerializationConfig().addMixInAnnotations(GenericWrapperField.class, IgnoreSetValueMixIn.class);
-    }};
     private static final Map<UserProperty, String> XPATH_EXPRESSIONS = new HashMap<UserProperty, String>() {{
         put(UserProperty.userAuths, "//userAuths/userAuth");
         put(UserProperty.authId, "authId");
diff --git a/src/main/java/org/nrg/xnat/restlet/presentation/RESTHTMLPresenter.java b/src/main/java/org/nrg/xnat/restlet/presentation/RESTHTMLPresenter.java
index c81f0c9a98529de8a2b2108ec07239b89495c255..a878e16d3448d8dc4f4d7aa4fdaab2b2db8fde95 100644
--- a/src/main/java/org/nrg/xnat/restlet/presentation/RESTHTMLPresenter.java
+++ b/src/main/java/org/nrg/xnat/restlet/presentation/RESTHTMLPresenter.java
@@ -20,6 +20,7 @@ import java.util.Iterator;
 import java.util.Map;
 import java.util.Random;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 import org.nrg.xdat.display.DisplayFieldReferenceI;
 import org.nrg.xdat.display.DisplayManager;
@@ -38,7 +39,7 @@ import org.nrg.xft.XFTTableI;
 import org.nrg.xft.db.ViewManager;
 import org.nrg.xft.schema.design.SchemaElementI;
 import org.nrg.xft.security.UserI;
-import org.nrg.xft.utils.StringUtils;
+import org.nrg.xft.utils.XftStringUtils;
 /**
  * @author Tim
  *
@@ -208,7 +209,7 @@ public class RESTHTMLPresenter extends PresentationA {
 				    SchemaElementI foreign = SchemaElement.GetElement(dfr.getElementName());
 				    if (search.isMultipleRelationship(foreign))
 				    {
-					    String temp = StringUtils.SQLMaxCharsAbbr(search.getRootElement().getSQLName() + "_" + foreign.getSQLName() + "_DIFF");
+					    String temp = XftStringUtils.SQLMaxCharsAbbr(search.getRootElement().getSQLName() + "_" + foreign.getSQLName() + "_DIFF");
 					    Integer index = ((XFTTable)table).getColumnIndex(temp);
 					    if (index!=null)
 					    {
@@ -301,7 +302,7 @@ public class RESTHTMLPresenter extends PresentationA {
 					    if (search.isMultipleRelationship(foreign))
 					    {
 							//XFT.LogCurrentTime("BEGIN HTML FORMAT :: ROWS(" + table.getRowCursor() + ") FIELDS: diffs :IF:1");
-						    String temp = StringUtils.SQLMaxCharsAbbr(search.getRootElement().getSQLName() + "_" + foreign.getSQLName() + "_DIFF");
+						    String temp = XftStringUtils.SQLMaxCharsAbbr(search.getRootElement().getSQLName() + "_" + foreign.getSQLName() + "_DIFF");
 						    Integer index = ((XFTTable)table).getColumnIndex(temp);
 						    if (index!=null)
 						    {
@@ -399,10 +400,10 @@ public class RESTHTMLPresenter extends PresentationA {
 								if (secureVariable != null)
 								{
                                     if (secureVariable.toString().indexOf("<")!=-1){
-                                        secureVariable = StringUtils.ReplaceStr(secureVariable.toString(), "<", "");
+                                        secureVariable = StringUtils.replace(secureVariable.toString(), "<", "");
                                     }
                                     if (secureVariable.toString().indexOf(">")!=-1){
-                                        secureVariable = StringUtils.ReplaceStr(secureVariable.toString(), ">", "");
+                                        secureVariable = StringUtils.replace(secureVariable.toString(), ">", "");
                                     }
 									values.put((String)link.getSecureProps().get(key),secureVariable.toString());
 								}
@@ -435,14 +436,14 @@ public class RESTHTMLPresenter extends PresentationA {
                                                             id = id.substring(6);
                                                             try {
                                                                 Integer i = Integer.parseInt(id);
-                                                                ArrayList<String> al = StringUtils.CommaDelimitedStringToArrayList(insertValue.toString());
+                                                                ArrayList<String> al = XftStringUtils.CommaDelimitedStringToArrayList(insertValue.toString());
                                                                 insertValue =al.get(i);
                                                             } catch (Throwable e) {
                                                                 logger.error("",e);
                                                             }
                                                         }
                                                     }
-                                                    value = StringUtils.ReplaceStr(value,"@" + key,insertValue.toString());
+                                                    value = StringUtils.replace(value,"@" + key,insertValue.toString());
                                                 }
                                             }else{
                                                 Object insertValue = row.get(id.toLowerCase());
@@ -454,13 +455,13 @@ public class RESTHTMLPresenter extends PresentationA {
                                                 {
                                                     insertValue = "NULL";
                                                 }
-                                                value = StringUtils.ReplaceStr(value,"@" + key,insertValue.toString());
+                                                value = StringUtils.replace(value,"@" + key,insertValue.toString());
                                             }
 										}
-										value = StringUtils.ReplaceStr(value,"@WEBAPP",server);
+										value = StringUtils.replace(value,"@WEBAPP",server);
 										sb.append(" ").append(prop.getName().toLowerCase()).append("=");
 										
-										if(StringUtils.IsEmpty(value)){
+										if(StringUtils.isBlank(value)){
 											sb.append("\"&nbsp;\"");
 										}else{
 											sb.append("\"").append(value).append("\"");
@@ -493,14 +494,14 @@ public class RESTHTMLPresenter extends PresentationA {
                                                     id = id.substring(6);
                                                     try {
                                                         Integer i = Integer.parseInt(id);
-                                                        ArrayList<String> al = StringUtils.CommaDelimitedStringToArrayList(insertValue.toString());
+                                                        ArrayList<String> al = XftStringUtils.CommaDelimitedStringToArrayList(insertValue.toString());
                                                         insertValue =al.get(i);
                                                     } catch (Throwable e) {
                                                         logger.error("",e);
                                                     }
                                                 }
                                             }
-                                            value = StringUtils.ReplaceStr(value,"@" + key,insertValue.toString());
+                                            value = StringUtils.replace(value,"@" + key,insertValue.toString());
                                         }
                                     }else{
                                         Object insertValue = row.get(id.toLowerCase());
@@ -512,13 +513,13 @@ public class RESTHTMLPresenter extends PresentationA {
                                         {
                                             insertValue = "NULL";
                                         }
-                                        value = StringUtils.ReplaceStr(value,"@" + key,insertValue.toString());
+                                        value = StringUtils.replace(value,"@" + key,insertValue.toString());
                                     }
 								}
-								value = StringUtils.ReplaceStr(value,"@WEBAPP",server);
+								value = StringUtils.replace(value,"@WEBAPP",server);
 								sb.append(" ").append(prop.getName()).append("=");
 
-								if(StringUtils.IsEmpty(value)){
+								if(StringUtils.isBlank(value)){
 									sb.append("\"&nbsp;\"");
 								}else{
 									sb.append("\"").append(value).append("\"");
@@ -534,10 +535,10 @@ public class RESTHTMLPresenter extends PresentationA {
 					if (dfr.isImage())
 					{
 						sb.append("<img");
-                        v = StringUtils.ReplaceStr((String)v,"/@WEBAPP/",server);
-                        v = StringUtils.ReplaceStr((String)v,"@WEBAPP/",server);
-                        v = StringUtils.ReplaceStr((String)v,"/@WEBAPP",server);
-                        v = StringUtils.ReplaceStr((String)v,"@WEBAPP",server);
+                        v = StringUtils.replace((String)v,"/@WEBAPP/",server);
+                        v = StringUtils.replace((String)v,"@WEBAPP/",server);
+                        v = StringUtils.replace((String)v,"/@WEBAPP",server);
+                        v = StringUtils.replace((String)v,"@WEBAPP",server);
 						if (dfr.getDisplayField().getHtmlImage().getWidth() != null)
 						{
 							sb.append(" width=\"" + dfr.getDisplayField().getHtmlImage().getWidth() + "\"");
@@ -557,7 +558,7 @@ public class RESTHTMLPresenter extends PresentationA {
                             String vS = v.toString();
                             if (vS.indexOf("<")!=-1 && vS.indexOf(">")==-1)
                             {
-                                vS= StringUtils.ReplaceStr(vS, "<", "&#60;");
+                                vS= StringUtils.replace(vS, "<", "&#60;");
                             }
 							sb.append(vS);
 					    }
diff --git a/src/main/java/org/nrg/xnat/restlet/resources/AutomationResource.java b/src/main/java/org/nrg/xnat/restlet/resources/AutomationResource.java
index 77d98174a1fcb1d5b7c52c04c55c8eccfad4c389..c0d62b297e404d0fc4dae92f524856a2d6cb75c1 100644
--- a/src/main/java/org/nrg/xnat/restlet/resources/AutomationResource.java
+++ b/src/main/java/org/nrg/xnat/restlet/resources/AutomationResource.java
@@ -1,7 +1,9 @@
 package org.nrg.xnat.restlet.resources;
 
 import org.apache.commons.lang.StringUtils;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.utils.URLEncodedUtils;
+import org.nrg.action.ServerException;
 import org.nrg.framework.constants.Scope;
 import org.nrg.xdat.om.XnatProjectdata;
 import org.nrg.xft.XFTTable;
@@ -11,19 +13,27 @@ import org.nrg.xft.event.persist.PersistentWorkflowI;
 import org.nrg.xft.event.persist.PersistentWorkflowUtils;
 import org.nrg.xnat.utils.WorkflowUtils;
 import org.restlet.Context;
+import org.restlet.data.MediaType;
 import org.restlet.data.Request;
 import org.restlet.data.Response;
 import org.restlet.data.Status;
+import org.restlet.resource.Representation;
 import org.restlet.resource.ResourceException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.IOException;
+import java.nio.charset.Charset;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.Properties;
 
+@SuppressWarnings("WeakerAccess")
 public abstract class AutomationResource extends SecureResource {
 
-    public static final String SITE_SCOPE = Scope.encode(Scope.Site, "");
+    static final         String  SITE_SCOPE      = Scope.encode(Scope.Site, "");
+    private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
 
     public AutomationResource(final Context context, final Request request, final Response response) throws ResourceException {
         super(context, request, response);
@@ -152,6 +162,29 @@ public abstract class AutomationResource extends SecureResource {
         return buffer.toString();
     }
 
+    protected Properties decodeProperties(final Representation entity, final MediaType mediaType) throws ServerException {
+        final Properties properties;
+        if (mediaType.equals(MediaType.APPLICATION_WWW_FORM)) {
+            try {
+                final List<NameValuePair> formMap = URLEncodedUtils.parse(entity.getText(), DEFAULT_CHARSET);
+                properties = new Properties();
+                for (final NameValuePair entry : formMap) {
+                    properties.setProperty(entry.getName(), entry.getValue());
+                }
+            } catch (IOException e) {
+                throw new ServerException(Status.SERVER_ERROR_INTERNAL, "An error occurred trying to read the submitted form body.", e);
+            }
+        } else {
+            try {
+                final String text = entity.getText();
+                properties = getSerializer().deserializeJson(text, Properties.class);
+            } catch (IOException e) {
+                throw new ServerException(Status.SERVER_ERROR_INTERNAL, "An error occurred processing the script properties", e);
+            }
+        }
+        return properties;
+    }
+
     private Map<String, String> validateEntityId(final String entityId) throws ResourceException {
         if (getScope() == null) {
             return null;
@@ -191,8 +224,6 @@ public abstract class AutomationResource extends SecureResource {
         }
     }
 
-    protected static final ObjectMapper MAPPER = new ObjectMapper();
-
     private static final Logger _log = LoggerFactory.getLogger(AutomationResource.class);
     private static final String ENTITY_ID = "ENTITY_ID";
     private static final String PROJECT_ID = "PROJECT_ID";
@@ -201,6 +232,7 @@ public abstract class AutomationResource extends SecureResource {
     private static final String KEY_PROJECTID = "projectId";
     private Scope _scope;
     private boolean _hasProjectId;
+
     private String _projectId;
 
     private final String _path;
diff --git a/src/main/java/org/nrg/xnat/restlet/resources/EventResource.java b/src/main/java/org/nrg/xnat/restlet/resources/EventResource.java
index 14b078f347275d05ac8c8c49e0468882f04e484e..928689f8d727f946d1340afd128dcad2edb07657 100644
--- a/src/main/java/org/nrg/xnat/restlet/resources/EventResource.java
+++ b/src/main/java/org/nrg/xnat/restlet/resources/EventResource.java
@@ -35,8 +35,8 @@ import java.util.Properties;
 
 public class EventResource extends AutomationResource {
 
-    public static final String PROPERTY_EVENT_ID = "event_id";
-    public static final String PROPERTY_EVENT_LABEL = "event_label";
+    private static final String PROPERTY_EVENT_ID    = "event_id";
+    private static final String PROPERTY_EVENT_LABEL = "event_label";
 
     public EventResource(Context context, Request request, Response response) throws ResourceException {
         super(context, request, response);
@@ -142,7 +142,7 @@ public class EventResource extends AutomationResource {
                     final Map<String, String> event = new HashMap<>();
                     event.put(PROPERTY_EVENT_ID, getResourceId());
                     event.put(PROPERTY_EVENT_LABEL, label);
-                    return new StringRepresentation(MAPPER.writeValueAsString(event), mediaType);
+                    return new StringRepresentation(getSerializer().toJson(event), mediaType);
                 }
             } else {
                 // They're asking for list of existing script events, so give them that.
@@ -213,7 +213,7 @@ public class EventResource extends AutomationResource {
                 } else {
                     try {
                         final String text = entity.getText();
-                        _properties.putAll(MAPPER.readValue(text, Properties.class));
+                        _properties.putAll(getSerializer().deserializeJson(text, Properties.class));
                     } catch (IOException e) {
                         throw new ResourceException(Status.SERVER_ERROR_INTERNAL, "An error occurred processing the script properties", e);
                     }
diff --git a/src/main/java/org/nrg/xnat/restlet/resources/ExperimentResource.java b/src/main/java/org/nrg/xnat/restlet/resources/ExperimentResource.java
index 1e1cbc0184b2156676ed31ffaefe1594db03e88c..9b4c07ee446227f8cec7688e74efa08dd553e340 100644
--- a/src/main/java/org/nrg/xnat/restlet/resources/ExperimentResource.java
+++ b/src/main/java/org/nrg/xnat/restlet/resources/ExperimentResource.java
@@ -31,6 +31,7 @@ import org.nrg.xft.event.persist.PersistentWorkflowUtils;
 import org.nrg.xft.exception.InvalidValueException;
 import org.nrg.xft.utils.SaveItemHelper;
 import org.nrg.xft.utils.ValidationUtils.ValidationResults;
+import org.nrg.xft.utils.XftStringUtils;
 import org.nrg.xnat.archive.Rename;
 import org.nrg.xnat.archive.ValidationException;
 import org.nrg.xnat.exceptions.InvalidArchiveStructure;
@@ -413,7 +414,7 @@ public class ExperimentResource extends ItemResource {
                 }
 
 
-                if (StringUtils.isNotBlank(_experiment.getLabel()) && !org.nrg.xft.utils.StringUtils.IsAlphaNumericUnderscore(_experiment.getId())) {
+                if (StringUtils.isNotBlank(_experiment.getLabel()) && !XftStringUtils.IsAlphaNumericUnderscore(_experiment.getId())) {
                     getResponse().setStatus(Status.CLIENT_ERROR_EXPECTATION_FAILED, "Invalid character in experiment label.");
                     return;
                 }
diff --git a/src/main/java/org/nrg/xnat/restlet/resources/ExptAssessmentResource.java b/src/main/java/org/nrg/xnat/restlet/resources/ExptAssessmentResource.java
index 4a7d91ecb0041b2f09369f16de6a7ea1ce9a4c83..5e784d5c957002b8ba73e3677bfce6aac6ff11a2 100644
--- a/src/main/java/org/nrg/xnat/restlet/resources/ExptAssessmentResource.java
+++ b/src/main/java/org/nrg/xnat/restlet/resources/ExptAssessmentResource.java
@@ -10,6 +10,7 @@
  */
 package org.nrg.xnat.restlet.resources;
 
+import org.apache.commons.lang3.StringUtils;
 import org.nrg.action.ActionException;
 import org.nrg.xdat.base.BaseElement;
 import org.nrg.xdat.model.XnatExperimentdataShareI;
@@ -30,7 +31,7 @@ import org.nrg.xft.event.persist.PersistentWorkflowUtils.EventRequirementAbsent;
 import org.nrg.xft.exception.InvalidValueException;
 import org.nrg.xft.security.UserI;
 import org.nrg.xft.utils.SaveItemHelper;
-import org.nrg.xft.utils.StringUtils;
+import org.nrg.xft.utils.XftStringUtils;
 import org.nrg.xft.utils.ValidationUtils.ValidationResults;
 import org.nrg.xnat.helpers.xmlpath.XMLPathShortcuts;
 import org.nrg.xnat.utils.WorkflowUtils;
@@ -315,7 +316,7 @@ public class ExptAssessmentResource extends ItemResource {
 						allowDataDeletion=true;
 					}
 
-					if(!StringUtils.IsEmpty(assessor.getLabel()) && !StringUtils.IsAlphaNumericUnderscore(assessor.getId())){
+					if(StringUtils.isNotBlank(assessor.getLabel()) && !XftStringUtils.IsAlphaNumericUnderscore(assessor.getId())){
 						this.getResponse().setStatus(Status.CLIENT_ERROR_EXPECTATION_FAILED,"Invalid character in experiment label.");
 						return;
 					}
diff --git a/src/main/java/org/nrg/xnat/restlet/resources/ProjSubExptAsstList.java b/src/main/java/org/nrg/xnat/restlet/resources/ProjSubExptAsstList.java
index 8a612d3ea7661e5744a1c71143e81f7bea68f8cc..f489e01e88dbb5994bc5b62531908f011830868f 100644
--- a/src/main/java/org/nrg/xnat/restlet/resources/ProjSubExptAsstList.java
+++ b/src/main/java/org/nrg/xnat/restlet/resources/ProjSubExptAsstList.java
@@ -10,6 +10,7 @@
  */
 package org.nrg.xnat.restlet.resources;
 
+import org.apache.commons.lang3.StringUtils;
 import org.nrg.action.ActionException;
 import org.nrg.xdat.base.BaseElement;
 import org.nrg.xdat.model.XnatExperimentdataShareI;
@@ -26,7 +27,7 @@ import org.nrg.xft.schema.Wrappers.GenericWrapper.GenericWrapperElement;
 import org.nrg.xft.search.CriteriaCollection;
 import org.nrg.xft.search.QueryOrganizer;
 import org.nrg.xft.security.UserI;
-import org.nrg.xft.utils.StringUtils;
+import org.nrg.xft.utils.XftStringUtils;
 import org.nrg.xft.utils.ValidationUtils.ValidationResults;
 import org.nrg.xnat.helpers.xmlpath.XMLPathShortcuts;
 import org.restlet.Context;
@@ -235,7 +236,7 @@ public class ProjSubExptAsstList extends QueryOrganizerResource {
 						allowDataDeletion=true;
 					}
 					
-				if(!StringUtils.IsEmpty(assessor.getLabel()) && !StringUtils.IsAlphaNumericUnderscore(assessor.getId())){
+				if(StringUtils.isNotBlank(assessor.getLabel()) && !XftStringUtils.IsAlphaNumericUnderscore(assessor.getId())){
 					this.getResponse().setStatus(Status.CLIENT_ERROR_EXPECTATION_FAILED,"Invalid character in experiment label.");
 					return;
 				}
diff --git a/src/main/java/org/nrg/xnat/restlet/resources/ProjSubExptList.java b/src/main/java/org/nrg/xnat/restlet/resources/ProjSubExptList.java
index cdbf46a02b63adce87893854f1fcfd9340a94e56..516d144a41890a46dcad63e2cfcc90a4532c432c 100644
--- a/src/main/java/org/nrg/xnat/restlet/resources/ProjSubExptList.java
+++ b/src/main/java/org/nrg/xnat/restlet/resources/ProjSubExptList.java
@@ -10,6 +10,7 @@
  */
 package org.nrg.xnat.restlet.resources;
 
+import org.apache.commons.lang3.StringUtils;
 import org.nrg.action.ActionException;
 import org.nrg.xdat.base.BaseElement;
 import org.nrg.xdat.model.XnatExperimentdataShareI;
@@ -28,7 +29,7 @@ import org.nrg.xft.schema.design.SchemaElementI;
 import org.nrg.xft.search.CriteriaCollection;
 import org.nrg.xft.search.QueryOrganizer;
 import org.nrg.xft.security.UserI;
-import org.nrg.xft.utils.StringUtils;
+import org.nrg.xft.utils.XftStringUtils;
 import org.nrg.xft.utils.ValidationUtils.ValidationResults;
 import org.nrg.xnat.helpers.xmlpath.XMLPathShortcuts;
 import org.nrg.xnat.restlet.actions.TriggerPipelines;
@@ -232,7 +233,7 @@ public class ProjSubExptList extends SubjAssessmentAbst {
 						allowDataDeletion=true;
 					}
 
-				if(!StringUtils.IsEmpty(expt.getLabel()) && !StringUtils.IsAlphaNumericUnderscore(expt.getId())){
+				if(StringUtils.isNotBlank(expt.getLabel()) && !XftStringUtils.IsAlphaNumericUnderscore(expt.getId())){
 					this.getResponse().setStatus(Status.CLIENT_ERROR_EXPECTATION_FAILED,"Invalid character in experiment label.");
 					return;
 				}
diff --git a/src/main/java/org/nrg/xnat/restlet/resources/ProjectListResource.java b/src/main/java/org/nrg/xnat/restlet/resources/ProjectListResource.java
index 61c1d1073b8f559a147c6cc890e5c06832bac5eb..59122f5ca4649e27bd1096c5d90c8c3addea2f2a 100644
--- a/src/main/java/org/nrg/xnat/restlet/resources/ProjectListResource.java
+++ b/src/main/java/org/nrg/xnat/restlet/resources/ProjectListResource.java
@@ -12,6 +12,7 @@ package org.nrg.xnat.restlet.resources;
 
 import com.google.common.base.Joiner;
 import com.google.common.collect.Lists;
+import org.apache.commons.lang3.StringUtils;
 import org.nrg.action.ActionException;
 import org.nrg.xdat.om.XdatStoredSearch;
 import org.nrg.xdat.om.XnatProjectdata;
@@ -34,7 +35,7 @@ import org.nrg.xft.search.CriteriaCollection;
 import org.nrg.xft.search.QueryOrganizer;
 import org.nrg.xft.security.UserI;
 import org.nrg.xft.utils.DateUtils;
-import org.nrg.xft.utils.StringUtils;
+import org.nrg.xft.utils.XftStringUtils;
 import org.nrg.xnat.helpers.xmlpath.XMLPathShortcuts;
 import org.nrg.xnat.restlet.representations.ItemXMLRepresentation;
 import org.restlet.Context;
@@ -99,12 +100,12 @@ public class ProjectListResource extends QueryOrganizerResource {
             if (item.instanceOf("xnat:projectData")) {
                 XnatProjectdata project = new XnatProjectdata(item);
 
-                if (StringUtils.IsEmpty(project.getId())) {
+                if (StringUtils.isBlank(project.getId())) {
                     this.getResponse().setStatus(Status.CLIENT_ERROR_EXPECTATION_FAILED, "Requires XNAT ProjectData ID");
                     return;
                 }
 
-                if (!StringUtils.IsAlphaNumericUnderscore(project.getId())) {
+                if (!XftStringUtils.IsAlphaNumericUnderscore(project.getId())) {
                     this.getResponse().setStatus(Status.CLIENT_ERROR_EXPECTATION_FAILED, "Invalid character in project ID.");
                     return;
                 }
@@ -505,7 +506,7 @@ public class ProjectListResource extends QueryOrganizerResource {
             table.initTable(columns);
 
             final String permissions = resource.getQueryVariable("permissions");
-            if (StringUtils.IsEmpty(permissions)) {
+            if (StringUtils.isBlank(permissions)) {
                 throw new Exception("You must specify a value for the permissions parameter.");
             } else if (!PERMISSIONS.contains(permissions)) {
                 throw new Exception("You must specify one of the following values for the permissions parameter: " + Joiner.on(", ").join(PERMISSIONS));
@@ -519,7 +520,7 @@ public class ProjectListResource extends QueryOrganizerResource {
                     final String projectId = (String) key;
                     // If no data type is specified, we check both MR and PET session data permissions. This is basically
                     // tailored for checking for projects to which the user can upload imaging data.
-                    final boolean canEdit = StringUtils.IsEmpty(dataType) ? userHelperService.hasEditAccessToSessionDataByTag(projectId) : Permissions.can(resource.user, dataType + "/project", projectId, permissions);
+                    final boolean canEdit = StringUtils.isBlank(dataType) ? userHelperService.hasEditAccessToSessionDataByTag(projectId) : Permissions.can(resource.user, dataType + "/project", projectId, permissions);
                     if (canEdit) {
                         table.insertRowItems(projectId, projects.get(projectId));
                     }
diff --git a/src/main/java/org/nrg/xnat/restlet/resources/ProjectPipelineListResource.java b/src/main/java/org/nrg/xnat/restlet/resources/ProjectPipelineListResource.java
index b81a49a931033297d58a757f4986fa6f6e83b689..d39440c5619a0955326a55f8cc2991b89697e070 100644
--- a/src/main/java/org/nrg/xnat/restlet/resources/ProjectPipelineListResource.java
+++ b/src/main/java/org/nrg/xnat/restlet/resources/ProjectPipelineListResource.java
@@ -87,7 +87,7 @@ public class ProjectPipelineListResource extends SecureResource  {
 					            }
 					        }
 							//Send a 200 OK message back
-							//getResponse().setStatus(Status.SUCCESS_OK,"Pipeline has been removed from project " + proj.getId());
+							//getResponse().setStatus(Status.SUCCESS_OK,"Pipeline has been removed from project " + _project.getId());
 						}
 						}catch(Exception e) {
 							e.printStackTrace();
diff --git a/src/main/java/org/nrg/xnat/restlet/resources/ProjectResource.java b/src/main/java/org/nrg/xnat/restlet/resources/ProjectResource.java
index 0e54a2a032bb792d95d2c0380c81603fe488d997..92b3a5e14f7f5e556e59d2f256acf9a4bc1c44f5 100644
--- a/src/main/java/org/nrg/xnat/restlet/resources/ProjectResource.java
+++ b/src/main/java/org/nrg/xnat/restlet/resources/ProjectResource.java
@@ -11,6 +11,7 @@
 package org.nrg.xnat.restlet.resources;
 
 import com.google.common.collect.Lists;
+import org.apache.commons.lang3.StringUtils;
 import org.nrg.action.ActionException;
 import org.nrg.xdat.XDAT;
 import org.nrg.xdat.om.ArcProject;
@@ -26,7 +27,7 @@ import org.nrg.xft.event.EventUtils;
 import org.nrg.xft.event.persist.PersistentWorkflowI;
 import org.nrg.xft.event.persist.PersistentWorkflowUtils;
 import org.nrg.xft.exception.InvalidPermissionException;
-import org.nrg.xft.utils.StringUtils;
+import org.nrg.xft.utils.XftStringUtils;
 import org.nrg.xnat.helpers.xmlpath.XMLPathShortcuts;
 import org.nrg.xnat.turbine.utils.ArcSpecManager;
 import org.nrg.xnat.utils.WorkflowUtils;
@@ -239,16 +240,16 @@ public class ProjectResource extends ItemResource {
                                 getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
                             }
                         } else {
-                            if (StringUtils.IsEmpty(project.getId())) {
+                            if (StringUtils.isBlank(project.getId())) {
                                 project.setId(projectId);
                             }
 
-                            if (StringUtils.IsEmpty(project.getId())) {
+                            if (StringUtils.isBlank(project.getId())) {
                                 getResponse().setStatus(Status.CLIENT_ERROR_EXPECTATION_FAILED, "Requires XNAT ProjectData ID");
                                 return;
                             }
 
-                            if (!StringUtils.IsAlphaNumericUnderscore(project.getId()) && !isQueryVariableTrue("testHyphen")) {
+                            if (!XftStringUtils.IsAlphaNumericUnderscore(project.getId()) && !isQueryVariableTrue("testHyphen")) {
                                 getResponse().setStatus(Status.CLIENT_ERROR_EXPECTATION_FAILED, "Invalid character in project ID.");
                                 return;
                             }
diff --git a/src/main/java/org/nrg/xnat/restlet/resources/ProjectSubjectList.java b/src/main/java/org/nrg/xnat/restlet/resources/ProjectSubjectList.java
index afc418e78b6ba5cc616f4546cbeab383af68d8ff..1f951985f72942db0b2c0b97cd9caac8608afd06 100644
--- a/src/main/java/org/nrg/xnat/restlet/resources/ProjectSubjectList.java
+++ b/src/main/java/org/nrg/xnat/restlet/resources/ProjectSubjectList.java
@@ -10,6 +10,7 @@
  */
 package org.nrg.xnat.restlet.resources;
 
+import org.apache.commons.lang3.StringUtils;
 import org.nrg.action.ActionException;
 import org.nrg.xdat.model.XnatProjectparticipantI;
 import org.nrg.xdat.om.XnatProjectdata;
@@ -25,7 +26,7 @@ import org.nrg.xft.schema.Wrappers.GenericWrapper.GenericWrapperElement;
 import org.nrg.xft.search.CriteriaCollection;
 import org.nrg.xft.search.QueryOrganizer;
 import org.nrg.xft.security.UserI;
-import org.nrg.xft.utils.StringUtils;
+import org.nrg.xft.utils.XftStringUtils;
 import org.nrg.xft.utils.ValidationUtils.ValidationResults;
 import org.nrg.xnat.helpers.xmlpath.XMLPathShortcuts;
 import org.restlet.Context;
@@ -150,7 +151,7 @@ public class ProjectSubjectList extends QueryOrganizerResource {
 						}
 				
 
-				if(!StringUtils.IsEmpty(sub.getLabel()) && !StringUtils.IsAlphaNumericUnderscore(sub.getId())){
+				if(StringUtils.isNotBlank(sub.getLabel()) && !XftStringUtils.IsAlphaNumericUnderscore(sub.getId())){
 					this.getResponse().setStatus(Status.CLIENT_ERROR_EXPECTATION_FAILED,"Invalid character in subject label.");
 					return;
 								}
@@ -190,7 +191,7 @@ public class ProjectSubjectList extends QueryOrganizerResource {
 	
 	@Override
 	public ArrayList<String> getDefaultFields(GenericWrapperElement e) {
-		ArrayList<String> al=new ArrayList<String>();
+		ArrayList<String> al= new ArrayList<>();
 		
 		al.add("ID");
 		al.add("project");
diff --git a/src/main/java/org/nrg/xnat/restlet/resources/ProjectUserListResource.java b/src/main/java/org/nrg/xnat/restlet/resources/ProjectUserListResource.java
index a36eb43908a51030bfe1a4d21dbfc5f27184aa2f..12a5322df1983e49ea4e95d1d9b5279a0813a7ba 100644
--- a/src/main/java/org/nrg/xnat/restlet/resources/ProjectUserListResource.java
+++ b/src/main/java/org/nrg/xnat/restlet/resources/ProjectUserListResource.java
@@ -17,16 +17,15 @@ import org.nrg.xdat.XDAT;
 import org.nrg.xdat.om.XnatProjectdata;
 import org.nrg.xdat.security.helpers.Permissions;
 import org.nrg.xdat.security.helpers.Roles;
-import org.nrg.xdat.security.helpers.UserHelper;
 import org.nrg.xft.XFTTable;
 import org.nrg.xft.exception.DBPoolException;
-import org.nrg.xft.exception.InvalidItemException;
 import org.restlet.Context;
 import org.restlet.data.MediaType;
 import org.restlet.data.Request;
 import org.restlet.data.Response;
 import org.restlet.data.Status;
 import org.restlet.resource.Representation;
+import org.restlet.resource.ResourceException;
 import org.restlet.resource.Variant;
 
 import java.io.IOException;
@@ -35,51 +34,50 @@ import java.util.Hashtable;
 import java.util.List;
 
 public class ProjectUserListResource extends SecureResource {
-    XFTTable table = null;
-    XnatProjectdata proj = null;
-    boolean displayHiddenUsers = false;
+    private final XnatProjectdata _project;
+    private final boolean         _displayHiddenUsers;
 
     public ProjectUserListResource(Context context, Request request, Response response) throws Exception {
         super(context, request, response);
 
+        setReadable(true);
+        setModifiable(false);
+
         getVariants().add(new Variant(MediaType.APPLICATION_JSON));
         getVariants().add(new Variant(MediaType.TEXT_HTML));
         getVariants().add(new Variant(MediaType.TEXT_XML));
 
         final String projectId = (String) getParameter(request, "PROJECT_ID");
-        if (projectId != null) {
-            proj = XnatProjectdata.getProjectByIDorAlias(projectId, user, false);
-        }
-        if (proj == null) {
+        _project = org.apache.commons.lang3.StringUtils.isNotBlank(projectId) ? XnatProjectdata.getProjectByIDorAlias(projectId, user, false) : null;
+        if (_project == null) {
+            _displayHiddenUsers = false;
             getResponse().setStatus(Status.CLIENT_ERROR_NOT_FOUND, "The project ID " + projectId + " does not appear to be a valid project ID. Please verify your information.");
         } else {
-                if (!(Roles.isSiteAdmin(user) || Permissions.canEdit(user, proj) || isWhitelisted(projectId))) {
-                    logger.error("Unauthorized Access to project-level user resources. User: " + userName);
-                    getResponse().setStatus(Status.CLIENT_ERROR_FORBIDDEN, "Access Denied: Only project owners and site managers can access user resources.");
-                }
-                displayHiddenUsers = Boolean.parseBoolean((String) getParameter(request, "DISPLAY_HIDDEN_USERS"));
+            if (!(Roles.isSiteAdmin(user) || Permissions.canEdit(user, _project) || isWhitelisted(projectId))) {
+                logger.error("Unauthorized Access to project-level user resources. User: " + userName);
+                getResponse().setStatus(Status.CLIENT_ERROR_FORBIDDEN, "Access Denied: Only project owners and site managers can access user resources.");
             }
+            _displayHiddenUsers = Boolean.parseBoolean((String) getParameter(request, "DISPLAY_HIDDEN_USERS"));
         }
-
-    @Override
-    public boolean allowGet() {
-        return true;
     }
 
     @Override
-    public Representation represent(Variant variant) {
+    public Representation represent(Variant variant) throws ResourceException {
 
-        if (proj != null) {
-            final StringBuilder query = new StringBuilder("SELECT g.id AS \"GROUP_ID\", displayname,login,firstname,lastname,email FROM xdat_userGroup g RIGHT JOIN xdat_user_Groupid map ON g.id=map.groupid RIGHT JOIN xdat_user u ON map.groups_groupid_xdat_user_xdat_user_id=u.xdat_user_id WHERE tag='").append(proj.getId()).append("' ");
+        final XFTTable table;
+        if (_project != null) {
+            final StringBuilder query = new StringBuilder("SELECT g.id AS \"GROUP_ID\", displayname,login,firstname,lastname,email FROM xdat_userGroup g RIGHT JOIN xdat_user_Groupid map ON g.id=map.groupid RIGHT JOIN xdat_user u ON map.groups_groupid_xdat_user_xdat_user_id=u.xdat_user_id WHERE tag='").append(_project.getId()).append("' ");
             try {
-                if(!displayHiddenUsers){
+                if(!_displayHiddenUsers){
                     query.append(" and enabled = 1 ");
                 }
                 query.append(" ORDER BY g.id DESC;");
                 table = XFTTable.Execute(query.toString(), user.getDBName(), user.getLogin());
             } catch (SQLException | DBPoolException e) {
-                logger.warn("An error occurred trying to run the following query: " + query.toString(), e);
+                throw new ResourceException(Status.SERVER_ERROR_INTERNAL, "An error occurred trying to run the following query: " + query.toString(), e);
             }
+        } else {
+            table = null;
         }
 
         Hashtable<String, Object> params = new Hashtable<>();
@@ -92,12 +90,12 @@ public class ProjectUserListResource extends SecureResource {
     }
 
     public boolean isWhitelisted() {
-        final String projectId = (String) proj.getItem().getProps().get("id");
+        final String projectId = (String) _project.getItem().getProps().get("id");
         final ConfigService configService = XDAT.getConfigService();
         final String config = configService.getConfigContents("user-resource-whitelist", "whitelist.json", Scope.Project, projectId);
         if (!StringUtils.isBlank(config)) {
             try {
-                List<String> projectUserResourceWhitelist = OBJECT_MAPPER.readValue(config, TYPE_REFERENCE_LIST_STRING);
+                List<String> projectUserResourceWhitelist = getSerializer().deserializeJson(config, TYPE_REFERENCE_LIST_STRING);
                 if (projectUserResourceWhitelist != null) {
                     return projectUserResourceWhitelist.contains(user.getUsername());
                 }
diff --git a/src/main/java/org/nrg/xnat/restlet/resources/QueryOrganizerResource.java b/src/main/java/org/nrg/xnat/restlet/resources/QueryOrganizerResource.java
index 2924cf10b248d08ffa59d2d9d18d250d6df54aed..6b25011ebd4f381567baef54fef87aee051579de 100644
--- a/src/main/java/org/nrg/xnat/restlet/resources/QueryOrganizerResource.java
+++ b/src/main/java/org/nrg/xnat/restlet/resources/QueryOrganizerResource.java
@@ -10,6 +10,7 @@
  */
 package org.nrg.xnat.restlet.resources;
 
+import org.apache.commons.lang3.StringUtils;
 import org.nrg.xft.TypeConverter.JavaMapping;
 import org.nrg.xft.TypeConverter.TypeConverter;
 import org.nrg.xft.XFTTable;
@@ -21,7 +22,7 @@ import org.nrg.xft.schema.Wrappers.GenericWrapper.GenericWrapperField;
 import org.nrg.xft.search.CriteriaCollection;
 import org.nrg.xft.search.QueryOrganizer;
 import org.nrg.xft.utils.DateUtils;
-import org.nrg.xft.utils.StringUtils;
+import org.nrg.xft.utils.XftStringUtils;
 import org.restlet.Context;
 import org.restlet.data.Request;
 import org.restlet.data.Response;
@@ -42,11 +43,11 @@ public abstract class QueryOrganizerResource extends SecureResource {
 
 	
 	public CriteriaCollection processStringQuery(String xmlPath, String values){
-		ArrayList<String> al=StringUtils.CommaDelimitedStringToArrayList(values);
+		ArrayList<String> al=XftStringUtils.CommaDelimitedStringToArrayList(values);
 		CriteriaCollection cc= new CriteriaCollection("OR");
 		for(String value:al){
 			if(value.contains("%") || value.contains("*")){
-				value=StringUtils.ReplaceStr(value, "*", "%");
+				value= StringUtils.replace(value, "*", "%");
 				cc.addClause(xmlPath, "LIKE", value);
 			}else{
 				cc.addClause(xmlPath, value);
@@ -56,7 +57,7 @@ public abstract class QueryOrganizerResource extends SecureResource {
 	}
 	
 	public CriteriaCollection processDateQuery(String column, String dates){
-		ArrayList<String> al=StringUtils.CommaDelimitedStringToArrayList(dates);
+		ArrayList<String> al=XftStringUtils.CommaDelimitedStringToArrayList(dates);
 		CriteriaCollection cc= new CriteriaCollection("OR");
 		for(String date:al){
 			if(date.contains("-")){
@@ -92,7 +93,7 @@ public abstract class QueryOrganizerResource extends SecureResource {
 	}
 	
 	public CriteriaCollection processNumericQuery(String column, String values){
-		ArrayList<String> al=StringUtils.CommaDelimitedStringToArrayList(values);
+		ArrayList<String> al=XftStringUtils.CommaDelimitedStringToArrayList(values);
 		CriteriaCollection cc= new CriteriaCollection("OR");
 		for(String date:al){
 			if(date.contains("-")){
@@ -111,7 +112,7 @@ public abstract class QueryOrganizerResource extends SecureResource {
 	}
 	
 	public CriteriaCollection processBooleanQuery(String column, String values){
-		ArrayList<String> al=StringUtils.CommaDelimitedStringToArrayList(values);
+		ArrayList<String> al=XftStringUtils.CommaDelimitedStringToArrayList(values);
 		CriteriaCollection cc= new CriteriaCollection("OR");
 		for(String value:al){
 			cc.addClause(column, value);
@@ -160,7 +161,7 @@ public abstract class QueryOrganizerResource extends SecureResource {
 	public void populateQuery(QueryOrganizer qo){
 		if(hasQueryVariable("columns") && !getQueryVariable("columns").equals("DEFAULT")){ 
 			try {
-				columns=StringUtils.CommaDelimitedStringToArrayList(URLDecoder.decode(getQueryVariable("columns"), "UTF-8"));
+				columns=XftStringUtils.CommaDelimitedStringToArrayList(URLDecoder.decode(getQueryVariable("columns"), "UTF-8"));
 			} catch (UnsupportedEncodingException e) {
 				logger.error("",e);
 				columns=getDefaultFields(qo.getRootElement());
@@ -216,7 +217,7 @@ public abstract class QueryOrganizerResource extends SecureResource {
 			}
 			
 			if(hasBodyVariable("columns")){
-				columns=StringUtils.CommaDelimitedStringToArrayList(getBodyVariable("columns"));
+				columns=XftStringUtils.CommaDelimitedStringToArrayList(getBodyVariable("columns"));
 				for(String col:columns){
 					if(col.contains("/")){
 						try {
@@ -305,7 +306,7 @@ public abstract class QueryOrganizerResource extends SecureResource {
 				}else if(this.fieldMapping.containsKey(key)){
 					fields.add(this.fieldMapping.get(key));
 				}else if(key.equals("columns")){
-					for(String col:StringUtils.CommaDelimitedStringToArrayList(getQueryVariable("columns"))){
+					for(String col:XftStringUtils.CommaDelimitedStringToArrayList(getQueryVariable("columns"))){
 						if(col.contains("/")){
 							fields.add(col);
 						}else if(this.fieldMapping.containsKey(col)){
@@ -317,7 +318,7 @@ public abstract class QueryOrganizerResource extends SecureResource {
 			
 			for(String field:fields){
 				try {
-					GenericWrapperElement ge=StringUtils.GetRootElement(field);
+					GenericWrapperElement ge=XftStringUtils.GetRootElement(field);
                     assert ge != null;
                     if(!ge.getXSIType().equals(rootElementName.getXSIType()) && ge.isExtensionOf(rootElementName)){
 						rootElementName=ge;
diff --git a/src/main/java/org/nrg/xnat/restlet/resources/ScriptResource.java b/src/main/java/org/nrg/xnat/restlet/resources/ScriptResource.java
index 6407ba30c303134227511a11a99ca53d64f109ab..92905f33715529813799ac44dc1fa818d0d313e7 100644
--- a/src/main/java/org/nrg/xnat/restlet/resources/ScriptResource.java
+++ b/src/main/java/org/nrg/xnat/restlet/resources/ScriptResource.java
@@ -1,8 +1,6 @@
 package org.nrg.xnat.restlet.resources;
 
 import org.apache.commons.lang.StringUtils;
-import org.apache.http.NameValuePair;
-import org.apache.http.client.utils.URLEncodedUtils;
 import org.nrg.action.ClientException;
 import org.nrg.action.ServerException;
 import org.nrg.automation.entities.Script;
@@ -23,7 +21,6 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
-import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.Hashtable;
 import java.util.List;
@@ -95,7 +92,7 @@ public class ScriptResource extends AutomationResource {
             try {
                 if (StringUtils.isNotBlank(_version)) {
                     //They're requesting a specific version of a specific script
-                    return new StringRepresentation(MAPPER.writeValueAsString(_scriptService.getVersion(_scriptId, _version)), mediaType);
+                    return new StringRepresentation(toJson(_scriptService.getVersion(_scriptId, _version)), mediaType);
                 }
                 else {
                     // They're requesting a specific script, so return that to them.
@@ -110,7 +107,7 @@ public class ScriptResource extends AutomationResource {
 
                     // have to check if it's null, or else it will return a StringRepresentation containing the word null instead of a 404
                     if (script != null) {
-                        return new StringRepresentation(MAPPER.writeValueAsString(script), mediaType);
+                        return new StringRepresentation(toJson(script), mediaType);
                     } else {
                         return null;
                     }
@@ -200,25 +197,7 @@ public class ScriptResource extends AutomationResource {
             throw new ClientException(Status.CLIENT_ERROR_UNSUPPORTED_MEDIA_TYPE, "This function currently only supports " + MediaType.APPLICATION_WWW_FORM + " and " + MediaType.APPLICATION_JSON);
         }
 
-        final Properties properties;
-        if (mediaType.equals(MediaType.APPLICATION_WWW_FORM)) {
-            try {
-                final List<NameValuePair> formMap = URLEncodedUtils.parse(entity.getText(), DEFAULT_CHARSET);
-                properties = new Properties();
-                for (final NameValuePair entry : formMap) {
-                    properties.setProperty(entry.getName(), entry.getValue());
-                }
-            } catch (IOException e) {
-                throw new ServerException(Status.SERVER_ERROR_INTERNAL, "An error occurred trying to read the submitted form body.", e);
-            }
-        } else {
-            try {
-                final String text = entity.getText();
-                properties = MAPPER.readValue(text, Properties.class);
-            } catch (IOException e) {
-                throw new ServerException(Status.SERVER_ERROR_INTERNAL, "An error occurred processing the script properties", e);
-            }
-        }
+        final Properties properties = decodeProperties(entity, mediaType);
 
         if (properties.containsKey("scriptId")) {
             properties.remove("scriptId");
@@ -253,7 +232,6 @@ public class ScriptResource extends AutomationResource {
 
     private static final String SCRIPT_ID = "SCRIPT_ID";
     private static final String VERSION = "VERSION";
-    private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
 
     private final ScriptService _scriptService;
     private final ScriptRunnerService _runnerService;
diff --git a/src/main/java/org/nrg/xnat/restlet/resources/ScriptRunnerResource.java b/src/main/java/org/nrg/xnat/restlet/resources/ScriptRunnerResource.java
index 18258397708b816d0b82d8bce965d1b449746d94..31c458d3f9c1ab0c9ccea2fac9adc76cecaf1556 100644
--- a/src/main/java/org/nrg/xnat/restlet/resources/ScriptRunnerResource.java
+++ b/src/main/java/org/nrg/xnat/restlet/resources/ScriptRunnerResource.java
@@ -1,7 +1,5 @@
 package org.nrg.xnat.restlet.resources;
 
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.apache.commons.lang.StringUtils;
 import org.nrg.automation.runners.ScriptRunner;
 import org.nrg.automation.services.ScriptRunnerService;
@@ -58,13 +56,13 @@ public class ScriptRunnerResource extends AutomationResource {
                 if (!_runnerService.hasRunner(_language)) {
                     throw new ResourceException(Status.CLIENT_ERROR_NOT_FOUND, String.format("No script runner found for %s", _language));
                 }
-                final String json = _serializer.writeValueAsString(_runnerService.getRunner(_language));
+                final String json = toJson(_runnerService.getRunner(_language));
                 return new StringRepresentation(json, mediaType);
             } else {
                 final List<String> runners = _runnerService.getRunners();
-                return new StringRepresentation(_serializer.writeValueAsString(runners), mediaType);
+                return new StringRepresentation(toJson(runners), mediaType);
             }
-        } catch (JsonProcessingException e) {
+        } catch (java.io.IOException e) {
             throw new ResourceException(Status.SERVER_ERROR_INTERNAL, "There was an error processing the script runners to JSON", e);
         }
     }
@@ -73,7 +71,6 @@ public class ScriptRunnerResource extends AutomationResource {
 
     private static final String LANGUAGE = "LANGUAGE";
 
-    private static final ObjectMapper _serializer = new ObjectMapper();
     private final ScriptRunnerService _runnerService;
     private final String _language;
 }
diff --git a/src/main/java/org/nrg/xnat/restlet/resources/ScriptTriggerResource.java b/src/main/java/org/nrg/xnat/restlet/resources/ScriptTriggerResource.java
index 1509ea080b85ebd8dc578ab7b2f54911a8e84254..4564240f0462576250900051802ada2ec4df64a4 100644
--- a/src/main/java/org/nrg/xnat/restlet/resources/ScriptTriggerResource.java
+++ b/src/main/java/org/nrg/xnat/restlet/resources/ScriptTriggerResource.java
@@ -1,8 +1,6 @@
 package org.nrg.xnat.restlet.resources;
 
 import org.apache.commons.lang.StringUtils;
-import org.apache.http.NameValuePair;
-import org.apache.http.client.utils.URLEncodedUtils;
 import org.nrg.action.ClientException;
 import org.nrg.action.ServerException;
 import org.nrg.automation.entities.ScriptTrigger;
@@ -22,7 +20,6 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
-import java.nio.charset.Charset;
 import java.util.*;
 
 public class ScriptTriggerResource extends AutomationResource {
@@ -124,7 +121,7 @@ public class ScriptTriggerResource extends AutomationResource {
         if (_trigger != null) {
             try {
                 // They're requesting a specific trigger, so return that to them.
-                return new StringRepresentation(MAPPER.writeValueAsString(mapTrigger(_trigger)), mediaType);
+                return new StringRepresentation(toJson(mapTrigger(_trigger)), mediaType);
             } catch (IOException e) {
                 throw new ResourceException(Status.SERVER_ERROR_INTERNAL, "An error occurred marshalling the script trigger data to JSON", e);
             }
@@ -245,25 +242,7 @@ public class ScriptTriggerResource extends AutomationResource {
             throw new ClientException(Status.CLIENT_ERROR_UNSUPPORTED_MEDIA_TYPE, "This function currently only supports " + MediaType.APPLICATION_WWW_FORM + " and " + MediaType.APPLICATION_JSON);
         }
 
-        final Properties properties;
-        if (mediaType.equals(MediaType.APPLICATION_WWW_FORM)) {
-            try {
-                final List<NameValuePair> formMap = URLEncodedUtils.parse(entity.getText(), DEFAULT_CHARSET);
-                properties = new Properties();
-                for (final NameValuePair entry : formMap) {
-                    properties.setProperty(entry.getName(), entry.getValue());
-                }
-            } catch (IOException e) {
-                throw new ServerException(Status.SERVER_ERROR_INTERNAL, "An error occurred trying to read the submitted form body.", e);
-            }
-        } else {
-            try {
-                final String text = entity.getText();
-                properties = MAPPER.readValue(text, Properties.class);
-            } catch (IOException e) {
-                throw new ServerException(Status.SERVER_ERROR_INTERNAL, "An error occurred processing the script properties", e);
-            }
-        }
+        final Properties properties = decodeProperties(entity, mediaType);
 
         // TODO: These remove definitions of scope, entity ID, and script ID that may be passed in on the API call.
         // TODO: We may consider throwing an exception if something in the body parameters contradicts the URI
@@ -364,7 +343,6 @@ public class ScriptTriggerResource extends AutomationResource {
 
     private static final String EVENT_ID = "EVENT_ID";
     private static final String TRIGGER_ID = "TRIGGER_ID";
-    private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
 
     private final EventService _eventService;
     private final ScriptTriggerService _scriptTriggerService;
diff --git a/src/main/java/org/nrg/xnat/restlet/resources/ScriptTriggerTemplateResource.java b/src/main/java/org/nrg/xnat/restlet/resources/ScriptTriggerTemplateResource.java
index a4318cad192091527e8b94431a87d13b448e604b..4ebd8205034dd6b4f209daeebab30585696fb367 100644
--- a/src/main/java/org/nrg/xnat/restlet/resources/ScriptTriggerTemplateResource.java
+++ b/src/main/java/org/nrg/xnat/restlet/resources/ScriptTriggerTemplateResource.java
@@ -2,7 +2,6 @@ package org.nrg.xnat.restlet.resources;
 
 import com.google.common.base.Joiner;
 import org.apache.commons.lang.StringUtils;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.nrg.action.ClientException;
 import org.nrg.automation.entities.ScriptTrigger;
 import org.nrg.automation.entities.ScriptTriggerTemplate;
@@ -224,7 +223,7 @@ public class ScriptTriggerTemplateResource extends AutomationResource {
         } else if (entity.getMediaType().equals(MediaType.APPLICATION_JSON)) {
             try {
                 final String text = entity.getText();
-                found = MAPPER.readValue(text, ScriptTriggerTemplate.class);
+                found = getSerializer().deserializeJson(text, ScriptTriggerTemplate.class);
             } catch (IOException e) {
                 throw new ClientException(Status.SERVER_ERROR_INTERNAL, "An error occurred processing the script properties", e);
             }
@@ -310,6 +309,4 @@ public class ScriptTriggerTemplateResource extends AutomationResource {
         }
         return persisted.size() > 0 ? persisted : null;
     }
-
-    private static final ObjectMapper MAPPER = new ObjectMapper();
 }
diff --git a/src/main/java/org/nrg/xnat/restlet/resources/ScriptVersionsResource.java b/src/main/java/org/nrg/xnat/restlet/resources/ScriptVersionsResource.java
index 4d62c1824fc4e387817d95eae1e6bce9da1f23b0..b017b3a32cf3b26115a0f0a71550b293427a9080 100644
--- a/src/main/java/org/nrg/xnat/restlet/resources/ScriptVersionsResource.java
+++ b/src/main/java/org/nrg/xnat/restlet/resources/ScriptVersionsResource.java
@@ -21,7 +21,6 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
-import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.Hashtable;
 import java.util.List;
@@ -88,7 +87,7 @@ public class ScriptVersionsResource extends AutomationResource {
 
                 // have to check if it's null, or else it will return a StringRepresentation containing the word null instead of a 404
                 if (versions != null) {
-                    return new StringRepresentation(MAPPER.writeValueAsString(versions), mediaType);
+                    return new StringRepresentation(toJson(versions), mediaType);
                 } else {
                     return null;
                 }
@@ -136,7 +135,6 @@ public class ScriptVersionsResource extends AutomationResource {
     private static final Logger _log = LoggerFactory.getLogger(ScriptVersionsResource.class);
 
     private static final String SCRIPT_ID = "SCRIPT_ID";
-    private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
 
     private final ScriptService _scriptService;
     private final ScriptRunnerService _runnerService;
diff --git a/src/main/java/org/nrg/xnat/restlet/resources/SecureResource.java b/src/main/java/org/nrg/xnat/restlet/resources/SecureResource.java
index 5fc9baa17314e52919d7972b02245a4b34bb0df4..f2d88bc8b39c2d7980a3e2cf40724757ce9aa064 100644
--- a/src/main/java/org/nrg/xnat/restlet/resources/SecureResource.java
+++ b/src/main/java/org/nrg/xnat/restlet/resources/SecureResource.java
@@ -11,7 +11,6 @@
 package org.nrg.xnat.restlet.resources;
 
 import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.common.collect.Maps;
 import com.noelios.restlet.http.HttpConstants;
 import org.apache.commons.beanutils.BeanUtils;
@@ -67,6 +66,7 @@ import org.nrg.xnat.restlet.representations.*;
 import org.nrg.xnat.restlet.util.FileWriterWrapperI;
 import org.nrg.xnat.restlet.util.RequestUtil;
 import org.nrg.xnat.turbine.utils.ArchivableItem;
+import org.nrg.xnat.utils.SerializerService;
 import org.nrg.xnat.utils.WorkflowUtils;
 import org.restlet.Context;
 import org.restlet.data.*;
@@ -137,9 +137,17 @@ public abstract class SecureResource extends Resource {
 
     protected String csrfToken = null;
 
+    private final SerializerService _serializer;
+
     public SecureResource(Context context, Request request, Response response) {
         super(context, request, response);
 
+        _serializer = XDAT.getContextService().getBean(SerializerService.class);
+        if (null == _serializer) {
+            getResponse().setStatus(Status.CLIENT_ERROR_FAILED_DEPENDENCY, "Serializer service was not properly initialized.");
+            throw new NrgServiceRuntimeException("ERROR: Serializer service was not properly initialized.");
+        }
+
         requested_format = getQueryVariable("format");
 
         // expects that the user exists in the session (either via traditional
@@ -247,6 +255,9 @@ public abstract class SecureResource extends Resource {
         return convertFormToMap(getQueryVariableForm());
     }
 
+    protected SerializerService getSerializer() {
+        return _serializer;
+    }
 
     private Form _body;
     private MediaType _mediaType;
@@ -519,6 +530,10 @@ public abstract class SecureResource extends Resource {
         }
     }
 
+    protected <T> String toJson(final T instance) throws IOException {
+        return getSerializer().toJson(instance);
+    }
+
     public interface ItemHandlerI {
         String getHandlerString();
 
@@ -1522,7 +1537,7 @@ public abstract class SecureResource extends Resource {
         }
 
         try {
-            List<String> userResourceWhitelist = OBJECT_MAPPER.readValue(config, TYPE_REFERENCE_LIST_STRING);
+            List<String> userResourceWhitelist = getSerializer().deserializeJson(config, TYPE_REFERENCE_LIST_STRING);
             if (userResourceWhitelist != null) {
                 return userResourceWhitelist.contains(user.getUsername());
             }
@@ -1534,9 +1549,7 @@ public abstract class SecureResource extends Resource {
         return false;
     }
 
-    protected final static TypeReference<ArrayList<String>> TYPE_REFERENCE_LIST_STRING = new TypeReference<ArrayList<String>>() {
-    };
-    protected final static ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+    protected final static TypeReference<ArrayList<String>> TYPE_REFERENCE_LIST_STRING = new TypeReference<ArrayList<String>>() {};
 
     private static Map<String, List<FilteredResourceHandlerI>> handlers = Maps.newConcurrentMap();
 
diff --git a/src/main/java/org/nrg/xnat/restlet/resources/SubjAssessmentResource.java b/src/main/java/org/nrg/xnat/restlet/resources/SubjAssessmentResource.java
index deeb72e779d00c1f5525ab37aa5da8a558ce2a72..5818f324a04ecb4bb83d8e144784f37389d6ca51 100644
--- a/src/main/java/org/nrg/xnat/restlet/resources/SubjAssessmentResource.java
+++ b/src/main/java/org/nrg/xnat/restlet/resources/SubjAssessmentResource.java
@@ -10,6 +10,7 @@
  */
 package org.nrg.xnat.restlet.resources;
 
+import org.apache.commons.lang3.StringUtils;
 import org.nrg.action.ActionException;
 import org.nrg.transaction.TransactionException;
 import org.nrg.xdat.base.BaseElement;
@@ -31,7 +32,7 @@ import org.nrg.xft.event.persist.PersistentWorkflowUtils.EventRequirementAbsent;
 import org.nrg.xft.exception.InvalidValueException;
 import org.nrg.xft.security.UserI;
 import org.nrg.xft.utils.SaveItemHelper;
-import org.nrg.xft.utils.StringUtils;
+import org.nrg.xft.utils.XftStringUtils;
 import org.nrg.xft.utils.ValidationUtils.ValidationResults;
 import org.nrg.xnat.archive.Rename;
 import org.nrg.xnat.archive.Rename.DuplicateLabelException;
@@ -335,7 +336,7 @@ public class SubjAssessmentResource extends SubjAssessmentAbst {
 					if(this.subject!=null){
 							expt.setSubjectId(this.subject.getId());
 					}else{
-						if(StringUtils.IsEmpty(expt.getSubjectId()) && org.apache.commons.lang.StringUtils.isNotEmpty(subID)){
+						if(StringUtils.isBlank(expt.getSubjectId()) && org.apache.commons.lang.StringUtils.isNotEmpty(subID)){
 							expt.setSubjectId(subID);
 						}
 
@@ -507,7 +508,7 @@ public class SubjAssessmentResource extends SubjAssessmentAbst {
 					}
 
 
-					if(!StringUtils.IsEmpty(expt.getLabel()) && !StringUtils.IsAlphaNumericUnderscore(expt.getId())){
+					if(StringUtils.isNotBlank(expt.getLabel()) && !XftStringUtils.IsAlphaNumericUnderscore(expt.getId())){
 						this.getResponse().setStatus(Status.CLIENT_ERROR_EXPECTATION_FAILED,"Invalid character in experiment label.");
 						return;
 					}
diff --git a/src/main/java/org/nrg/xnat/restlet/resources/SubjectResource.java b/src/main/java/org/nrg/xnat/restlet/resources/SubjectResource.java
index 59598ca642c7b58a146f64b4fe642ed8cfe27759..b5b2d008fdcee2ac6f20e1b36cfd0035c91cb44d 100644
--- a/src/main/java/org/nrg/xnat/restlet/resources/SubjectResource.java
+++ b/src/main/java/org/nrg/xnat/restlet/resources/SubjectResource.java
@@ -10,16 +10,12 @@
  */
 package org.nrg.xnat.restlet.resources;
 
+import org.apache.commons.lang3.StringUtils;
 import org.nrg.action.ActionException;
 import org.nrg.transaction.TransactionException;
 import org.nrg.xdat.model.XnatProjectdataI;
 import org.nrg.xdat.model.XnatProjectparticipantI;
-import org.nrg.xdat.om.XnatExperimentdata;
-import org.nrg.xdat.om.XnatImagesessiondata;
-import org.nrg.xdat.om.XnatProjectdata;
-import org.nrg.xdat.om.XnatProjectparticipant;
-import org.nrg.xdat.om.XnatSubjectassessordata;
-import org.nrg.xdat.om.XnatSubjectdata;
+import org.nrg.xdat.om.*;
 import org.nrg.xdat.om.base.BaseXnatSubjectdata;
 import org.nrg.xdat.security.helpers.Permissions;
 import org.nrg.xdat.security.helpers.Users;
@@ -34,8 +30,8 @@ import org.nrg.xft.event.persist.PersistentWorkflowUtils.EventRequirementAbsent;
 import org.nrg.xft.exception.InvalidValueException;
 import org.nrg.xft.security.UserI;
 import org.nrg.xft.utils.SaveItemHelper;
-import org.nrg.xft.utils.StringUtils;
 import org.nrg.xft.utils.ValidationUtils.ValidationResults;
+import org.nrg.xft.utils.XftStringUtils;
 import org.nrg.xnat.archive.Rename;
 import org.nrg.xnat.archive.Rename.DuplicateLabelException;
 import org.nrg.xnat.archive.Rename.FolderConflictException;
@@ -348,7 +344,7 @@ public class SubjectResource extends ItemResource {
                         sub.setProperty("xnat:subjectData/demographics[@xsi:type=xnat:demographicData]/gender", this.getQueryVariable("gender"));
                     }
 
-                    if (!StringUtils.IsEmpty(sub.getLabel()) && !StringUtils.IsAlphaNumericUnderscore(sub.getId())) {
+                    if (StringUtils.isNotBlank(sub.getLabel()) && !XftStringUtils.IsAlphaNumericUnderscore(sub.getId())) {
                         this.getResponse().setStatus(Status.CLIENT_ERROR_EXPECTATION_FAILED, "Invalid character in subject label.");
                         return;
                     }
@@ -560,7 +556,7 @@ public class SubjectResource extends ItemResource {
     public Representation representItem(XFTItem item, MediaType mt) {
         Representation representation = super.representItem(item, mt);
 
-        if (representation != null && proj != null && representation instanceof TurbineScreenRepresentation && StringUtils.HasContent(proj.getId())) {
+        if (representation != null && proj != null && representation instanceof TurbineScreenRepresentation && StringUtils.isNotBlank(proj.getId())) {
             // provides appropriate rendering if the caller is querying this subject in the context of a shared project
             ((TurbineScreenRepresentation) representation).setRunDataParameter("project", proj.getId());
         }
diff --git a/src/main/java/org/nrg/xnat/restlet/resources/files/CatalogResource.java b/src/main/java/org/nrg/xnat/restlet/resources/files/CatalogResource.java
index 2f69b1dc77e46ea7ed93ca21d99cdf145e359e02..89eb9a4b7c0270b88a0744696c1989124af47315 100644
--- a/src/main/java/org/nrg/xnat/restlet/resources/files/CatalogResource.java
+++ b/src/main/java/org/nrg/xnat/restlet/resources/files/CatalogResource.java
@@ -11,6 +11,7 @@
 package org.nrg.xnat.restlet.resources.files;
 
 import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.nrg.action.ActionException;
 import org.nrg.action.ClientException;
 import org.nrg.xdat.base.BaseElement;
@@ -25,7 +26,7 @@ import org.nrg.xft.event.persist.PersistentWorkflowUtils;
 import org.nrg.xft.exception.ElementNotFoundException;
 import org.nrg.xft.security.UserI;
 import org.nrg.xft.utils.SaveItemHelper;
-import org.nrg.xft.utils.StringUtils;
+import org.nrg.xft.utils.XftStringUtils;
 import org.nrg.xnat.restlet.representations.BeanRepresentation;
 import org.nrg.xnat.restlet.representations.ItemXMLRepresentation;
 import org.nrg.xnat.restlet.resources.ScanResource;
@@ -163,7 +164,7 @@ public class CatalogResource extends XNATCatalogTemplate {
 							for(String tag: tags){
 								tag = tag.trim();
 								if(!tag.equals("")){
-									for(String s:StringUtils.CommaDelimitedStringToArrayList(tag)){
+									for(String s:XftStringUtils.CommaDelimitedStringToArrayList(tag)){
 										s=s.trim();
 										if(!s.equals("")){
 											XnatAbstractresourceTag t = new XnatAbstractresourceTag((UserI)user);
@@ -197,7 +198,7 @@ public class CatalogResource extends XNATCatalogTemplate {
 									if(!"xnat_tools/AutoRun.xml".equals(wrk.getPipelineName())){
 										wrk=null;
 									}else{
-										if(StringUtils.IsEmpty(wrk.getCategory())){
+										if(StringUtils.isBlank(wrk.getCategory())){
 											wrk.setCategory(EventUtils.CATEGORY.DATA);
 											wrk.setType(EventUtils.TYPE.PROCESS);
 											WorkflowUtils.save(wrk, wrk.buildEvent());
@@ -225,12 +226,10 @@ public class CatalogResource extends XNATCatalogTemplate {
 					}
 				}else{
 					this.getResponse().setStatus(Status.CLIENT_ERROR_FORBIDDEN,"User account doesn't have permission to modify this session.");
-					return;
 				}
 
 			} catch (ActionException e) {
 				this.getResponse().setStatus(e.getStatus(),e.getMessage());
-				return;
 			} catch (Exception e) {
 				this.getResponse().setStatus(Status.SERVER_ERROR_INTERNAL,e.getMessage());
 				logger.error("",e);
@@ -315,7 +314,7 @@ public class CatalogResource extends XNATCatalogTemplate {
 	
 	private void getAllMatches(){
 		catalogs=null;
-		resources=new ArrayList<XnatAbstractresource>();
+		resources= new ArrayList<>();
 		try {
 			catalogs=this.loadCatalogs(resource_ids,false,true);
 		} catch (Exception e) {
diff --git a/src/main/java/org/nrg/xnat/restlet/resources/files/CatalogResourceList.java b/src/main/java/org/nrg/xnat/restlet/resources/files/CatalogResourceList.java
index 4b2605d6c91ed8dc96987286d4227442fb9af6dc..3ec4f8c9c9c987de96422df6112b3e2082c0b674 100644
--- a/src/main/java/org/nrg/xnat/restlet/resources/files/CatalogResourceList.java
+++ b/src/main/java/org/nrg/xnat/restlet/resources/files/CatalogResourceList.java
@@ -21,7 +21,7 @@ import org.nrg.xft.event.EventUtils;
 import org.nrg.xft.event.persist.PersistentWorkflowI;
 import org.nrg.xft.event.persist.PersistentWorkflowUtils;
 import org.nrg.xft.exception.ElementNotFoundException;
-import org.nrg.xft.utils.StringUtils;
+import org.nrg.xft.utils.XftStringUtils;
 import org.nrg.xnat.restlet.resources.ScanList;
 import org.nrg.xnat.turbine.utils.ArchivableItem;
 import org.nrg.xnat.utils.CatalogUtils;
@@ -115,7 +115,7 @@ public class CatalogResourceList extends XNATTemplate {
                     for(String tag: tags){
                         tag = tag.trim();
                         if(!tag.equals("")){
-                            for(String s:StringUtils.CommaDelimitedStringToArrayList(tag)){
+                            for(String s:XftStringUtils.CommaDelimitedStringToArrayList(tag)){
                                 s=s.trim();
                                 if(!s.equals("")){
                                     XnatAbstractresourceTag t = new XnatAbstractresourceTag(user);
diff --git a/src/main/java/org/nrg/xnat/restlet/resources/files/XNATCatalogTemplate.java b/src/main/java/org/nrg/xnat/restlet/resources/files/XNATCatalogTemplate.java
index 073cc732721b66c85458fdea7808c5767eef30a5..7d2027244c37237e0162f0c9d6a9ca229df5e032 100644
--- a/src/main/java/org/nrg/xnat/restlet/resources/files/XNATCatalogTemplate.java
+++ b/src/main/java/org/nrg/xnat/restlet/resources/files/XNATCatalogTemplate.java
@@ -15,7 +15,7 @@ import org.nrg.xft.XFTTable;
 import org.nrg.xft.event.EventMetaI;
 import org.nrg.xft.event.EventUtils;
 import org.nrg.xft.exception.ElementNotFoundException;
-import org.nrg.xft.utils.StringUtils;
+import org.nrg.xft.utils.XftStringUtils;
 import org.nrg.xnat.helpers.resource.XnatResourceInfo;
 import org.nrg.xnat.helpers.resource.direct.DirectResourceModifierBuilder;
 import org.nrg.xnat.helpers.resource.direct.ResourceModifierA;
@@ -41,8 +41,8 @@ public class XNATCatalogTemplate extends XNATTemplate {
 		String resourceID= (String)getParameter(request,"RESOURCE_ID");
 
 		if(resourceID!=null){
-			resource_ids=new ArrayList<String>();
-			for(String s:StringUtils.CommaDelimitedStringToArrayList(resourceID, true)){
+			resource_ids=new ArrayList<>();
+			for(String s:XftStringUtils.CommaDelimitedStringToArrayList(resourceID, true)){
 				resource_ids.add(s);
 				}
 			}
diff --git a/src/main/java/org/nrg/xnat/restlet/resources/files/XNATTemplate.java b/src/main/java/org/nrg/xnat/restlet/resources/files/XNATTemplate.java
index e654141ae11770e404e9902b699095dfaaa6cba9..7bce7fc3f91afeb419caf7dd1488a91e5e6393e3 100644
--- a/src/main/java/org/nrg/xnat/restlet/resources/files/XNATTemplate.java
+++ b/src/main/java/org/nrg/xnat/restlet/resources/files/XNATTemplate.java
@@ -21,7 +21,7 @@ import org.nrg.xft.schema.Wrappers.GenericWrapper.GenericWrapperElement;
 import org.nrg.xft.search.CriteriaCollection;
 import org.nrg.xft.utils.FileUtils;
 import org.nrg.xft.utils.SaveItemHelper;
-import org.nrg.xft.utils.StringUtils;
+import org.nrg.xft.utils.XftStringUtils;
 import org.nrg.xnat.exceptions.InvalidArchiveStructure;
 import org.nrg.xnat.restlet.resources.SecureResource;
 import org.nrg.xnat.restlet.util.XNATRestConstants;
@@ -98,7 +98,7 @@ public class XNATTemplate extends SecureResource {
 		String assessid = (String) getParameter(request,
 				"ASSESSED_ID");
 			if(assessid!=null){
-			for(String s: StringUtils.CommaDelimitedStringToArrayList(assessid)){
+			for(String s: XftStringUtils.CommaDelimitedStringToArrayList(assessid)){
 				XnatExperimentdata assessed = XnatImagesessiondata.getXnatImagesessiondatasById(
 					s, user, false);
 
@@ -132,7 +132,7 @@ public class XNATTemplate extends SecureResource {
 
 			String exptID= (String)getParameter(request,"EXPT_ID");
 			if(exptID!=null){
-			for(String s: StringUtils.CommaDelimitedStringToArrayList(exptID)){
+			for(String s: XftStringUtils.CommaDelimitedStringToArrayList(exptID)){
 				XnatExperimentdata expt = XnatExperimentdata.getXnatExperimentdatasById(s,
 						user, false);
 
@@ -203,7 +203,7 @@ public class XNATTemplate extends SecureResource {
 					subcc.addClause("xnat:imageScanData/ID", scanID);
 				}else{
 					CriteriaCollection subsubcc = new CriteriaCollection("OR");
-					for(String s:StringUtils.CommaDelimitedStringToArrayList(scanID, true)){
+					for(String s:XftStringUtils.CommaDelimitedStringToArrayList(scanID, true)){
 						subsubcc.addClause("xnat:imageScanData/ID", s);
 					}
 					subcc.add(subsubcc);
@@ -226,7 +226,7 @@ public class XNATTemplate extends SecureResource {
 					}
 				}else{
 					CriteriaCollection subsubcc = new CriteriaCollection("OR");
-					for(String s:StringUtils.CommaDelimitedStringToArrayList(scanID, true)){
+					for(String s:XftStringUtils.CommaDelimitedStringToArrayList(scanID, true)){
 						if(s.equals("NULL")){
 							subsubcc.addClause("xnat:imageScanData/type",""," IS NULL ",true);
 							subsubcc.addClause("xnat:imageScanData/type","");
@@ -264,7 +264,7 @@ public class XNATTemplate extends SecureResource {
 					subcc.addClause("xnat:reconstructedImageData/ID", reconID);
 				}else{
 					CriteriaCollection subsubcc = new CriteriaCollection("OR");
-					for(String s:StringUtils.CommaDelimitedStringToArrayList(reconID, true)){
+					for(String s:XftStringUtils.CommaDelimitedStringToArrayList(reconID, true)){
 						subsubcc.addClause("xnat:reconstructedImageData/ID", s);
 					}
 					subcc.add(subsubcc);
@@ -287,7 +287,7 @@ public class XNATTemplate extends SecureResource {
 					}
 				}else{
 					CriteriaCollection subsubcc = new CriteriaCollection("OR");
-					for(String s:StringUtils.CommaDelimitedStringToArrayList(reconID, true)){
+					for(String s:XftStringUtils.CommaDelimitedStringToArrayList(reconID, true)){
 						if(s.equals("NULL")){
 							subsubcc.addClause("xnat:reconstructedImageData/type",""," IS NULL ",true);
 							subsubcc.addClause("xnat:reconstructedImageData/type","");
diff --git a/src/main/java/org/nrg/xnat/restlet/resources/search/CachedSearchResource.java b/src/main/java/org/nrg/xnat/restlet/resources/search/CachedSearchResource.java
index e18dbefef2ebf8cd37aaecc52878182fb7644db2..9afcb2684ca8cc6b225e80c99825df10bf4bcca8 100644
--- a/src/main/java/org/nrg/xnat/restlet/resources/search/CachedSearchResource.java
+++ b/src/main/java/org/nrg/xnat/restlet/resources/search/CachedSearchResource.java
@@ -12,6 +12,7 @@ package org.nrg.xnat.restlet.resources.search;
 
 import com.noelios.restlet.ext.servlet.ServletCall;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 import org.nrg.xdat.search.DisplaySearch;
 import org.nrg.xdat.turbine.utils.AdminUtils;
@@ -20,7 +21,7 @@ import org.nrg.xft.XFTTable;
 import org.nrg.xft.db.MaterializedView;
 import org.nrg.xft.db.MaterializedViewI;
 import org.nrg.xft.db.PoolDBUtils;
-import org.nrg.xft.utils.StringUtils;
+import org.nrg.xft.utils.XftStringUtils;
 import org.nrg.xnat.restlet.presentation.RESTHTMLPresenter;
 import org.nrg.xnat.restlet.resources.SecureResource;
 import org.restlet.Context;
@@ -72,7 +73,7 @@ public class CachedSearchResource extends SecureResource {
 					response.setStatus(Status.CLIENT_ERROR_UNPROCESSABLE_ENTITY);
 					return;
 				}
-				sortBy=StringUtils.ReplaceStr(sortBy, " ", "");
+				sortBy= StringUtils.replace(sortBy, " ", "");
 			}
 			
 			if (this.getQueryVariable("sortOrder")!=null){
@@ -82,7 +83,7 @@ public class CachedSearchResource extends SecureResource {
 					response.setStatus(Status.CLIENT_ERROR_UNPROCESSABLE_ENTITY);
 					return;
 				}
-				sortOrder=StringUtils.ReplaceStr(sortOrder, " ", "");
+				sortOrder=StringUtils.replace(sortOrder, " ", "");
 			}
 			
 			this.getVariants().add(new Variant(MediaType.APPLICATION_JSON));
@@ -94,7 +95,7 @@ public class CachedSearchResource extends SecureResource {
 
 	@Override
 	public Representation getRepresentation(Variant variant) {	
-		Hashtable<String,Object> params=new Hashtable<String,Object>();
+		Hashtable<String,Object> params= new Hashtable<>();
 		if(tableName!=null){
 			params.put("ID", tableName);
 		}
diff --git a/src/main/java/org/nrg/xnat/restlet/resources/search/SearchFieldListResource.java b/src/main/java/org/nrg/xnat/restlet/resources/search/SearchFieldListResource.java
index 266f821976ec51e803d6423b9dd2187d40427fff..abc34721807be0576d33bb40e6844b46a41dd5b9 100644
--- a/src/main/java/org/nrg/xnat/restlet/resources/search/SearchFieldListResource.java
+++ b/src/main/java/org/nrg/xnat/restlet/resources/search/SearchFieldListResource.java
@@ -32,7 +32,7 @@ import org.nrg.xft.exception.FieldNotFoundException;
 import org.nrg.xft.exception.InvalidValueException;
 import org.nrg.xft.exception.XFTInitException;
 import org.nrg.xft.schema.Wrappers.GenericWrapper.GenericWrapperElement;
-import org.nrg.xft.utils.StringUtils;
+import org.nrg.xft.utils.XftStringUtils;
 import org.nrg.xnat.restlet.resources.SecureResource;
 import org.restlet.Context;
 import org.restlet.data.MediaType;
@@ -187,7 +187,7 @@ public class SearchFieldListResource extends SecureResource{
 		XFTTable fields = new XFTTable();
 		fields.initTable(new String[]{"FIELD_ID","HEADER","SUMMARY","TYPE","REQUIRES_VALUE","DESC","ELEMENT_NAME","SRC"});
 
-		ArrayList<String> elementNames=StringUtils.CommaDelimitedStringToArrayList(elementName);
+		ArrayList<String> elementNames=XftStringUtils.CommaDelimitedStringToArrayList(elementName);
 		for(String en : elementNames)
         {
             try {
diff --git a/src/main/java/org/nrg/xnat/restlet/resources/search/SearchResource.java b/src/main/java/org/nrg/xnat/restlet/resources/search/SearchResource.java
index 7b3bc4a9d84dcf778c1bca59565abf699567cd61..ec3d1116b0fdbb28be085d9666766c19b6ba7d08 100644
--- a/src/main/java/org/nrg/xnat/restlet/resources/search/SearchResource.java
+++ b/src/main/java/org/nrg/xnat/restlet/resources/search/SearchResource.java
@@ -21,6 +21,7 @@ import java.util.Map;
 
 import org.apache.commons.fileupload.FileItem;
 import org.apache.commons.fileupload.FileUploadException;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 import org.nrg.xdat.collections.DisplayFieldCollection.DisplayFieldNotFoundException;
 import org.nrg.xdat.display.DisplayFieldReferenceI;
@@ -44,7 +45,7 @@ import org.nrg.xft.exception.FieldNotFoundException;
 import org.nrg.xft.exception.XFTInitException;
 import org.nrg.xft.schema.Wrappers.XMLWrapper.SAXReader;
 import org.nrg.xft.security.UserI;
-import org.nrg.xft.utils.StringUtils;
+import org.nrg.xft.utils.XftStringUtils;
 import org.nrg.xnat.restlet.presentation.RESTHTMLPresenter;
 import org.nrg.xnat.restlet.resources.SecureResource;
 import org.restlet.Context;
@@ -241,8 +242,8 @@ public class SearchResource extends SecureResource {
 						ds.addKeyColumn(true);
 						
 						String query = ds.getSQLQuery(null);
-						query = StringUtils.ReplaceStr(query,"'","*'*");
-						query = StringUtils.ReplaceStr(query,"*'*","''");
+						query = StringUtils.replace(query, "'", "*'*");
+						query = StringUtils.replace(query,"*'*","''");
 
                         String codeToUse = getQueryVariable(MaterializedView.CACHING_HANDLER,MaterializedView.DEFAULT_MATERIALIZED_VIEW_SERVICE_CODE);
 						mv = MaterializedView.createView(user,codeToUse);
@@ -400,7 +401,7 @@ public class SearchResource extends SecureResource {
 							linkProps.append(prop.getName()).append("\"");
 							linkProps.append(",\"value\":\"");
 							String v =prop.getValue();
-							v = StringUtils.ReplaceStr(v,"@WEBAPP",TurbineUtils.GetRelativePath(ServletCall.getRequest(sr.getRequest())) + "/");
+							v = StringUtils.replace(v,"@WEBAPP",TurbineUtils.GetRelativePath(ServletCall.getRequest(sr.getRequest())) + "/");
 							
 							linkProps.append(v).append("\"");
 
@@ -426,7 +427,7 @@ public class SearchResource extends SecureResource {
 											        	insert_value = insert_value.substring(6);
 											            try {
 											                Integer i = Integer.parseInt(insert_value);
-											                ArrayList<String> al = StringUtils.CommaDelimitedStringToArrayList(insertValue.toString());
+											                ArrayList<String> al = XftStringUtils.CommaDelimitedStringToArrayList(insertValue.toString());
 											                insertValue =al.get(i);
 											            } catch (Throwable e) {
 											                logger.error("",e);
@@ -471,28 +472,24 @@ public class SearchResource extends SecureResource {
                     if (dfr.isImage()) {
 						cp.get(id).put("imgRoot", TurbineUtils.GetRelativePath(ServletCall.getRequest(sr.getRequest())) + "/");
 					}
-				} catch (XFTInitException e) {
-					logger.error("",e);
-				} catch (ElementNotFoundException e) {
+				} catch (XFTInitException | ElementNotFoundException e) {
 					logger.error("",e);
 				}
 
-				
+
 			}
 			
 			cp.put("quarantine_status",new Hashtable<String,String>());
-		} catch (ElementNotFoundException e) {
-			logger.error("",e);
-		} catch (XFTInitException e) {
+		} catch (ElementNotFoundException | XFTInitException e) {
 			logger.error("",e);
 		}
-		
+
 		return cp;
 	}
 
     private boolean canQueryByAllowedUser(final XdatStoredSearch search) {
         boolean allowed=false;
-        if(!StringUtils.IsEmpty(search.getId()))
+        if(StringUtils.isNotBlank(search.getId()))
         {
             //need to check against unmodified stored search
             final org.nrg.xdat.om.XdatStoredSearch stored = XdatStoredSearch.getXdatStoredSearchsById(search.getId(), user, true);
diff --git a/src/main/java/org/nrg/xnat/restlet/services/AliasTokenRestlet.java b/src/main/java/org/nrg/xnat/restlet/services/AliasTokenRestlet.java
index 042ce9e13f76650b78b650e4bd65dbbdb4e8d939..fd347ffa82d06f67cfbc5e2ccabc923182bf9d86 100644
--- a/src/main/java/org/nrg/xnat/restlet/services/AliasTokenRestlet.java
+++ b/src/main/java/org/nrg/xnat/restlet/services/AliasTokenRestlet.java
@@ -11,14 +11,13 @@
 package org.nrg.xnat.restlet.services;
 
 import com.google.common.collect.Maps;
-
 import org.apache.commons.lang.StringUtils;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.nrg.xdat.XDAT;
 import org.nrg.xdat.entities.AliasToken;
 import org.nrg.xdat.security.helpers.Roles;
 import org.nrg.xdat.services.AliasTokenService;
 import org.nrg.xnat.restlet.resources.SecureResource;
+import org.nrg.xnat.utils.SerializerService;
 import org.restlet.Context;
 import org.restlet.data.MediaType;
 import org.restlet.data.Request;
@@ -34,15 +33,15 @@ import java.util.HashMap;
 import java.util.Map;
 
 public class AliasTokenRestlet extends SecureResource {
-    public static final String PARAM_OPERATION = "OPERATION";
-    public static final String PARAM_USERNAME = "USERNAME";
-    public static final String PARAM_TOKEN = "TOKEN";
-    public static final String PARAM_SECRET = "SECRET";
-    public static final String OP_ISSUE = "issue";
-    public static final String OP_VALIDATE = "validate";
-    public static final String OP_INVALIDATE = "invalidate";
+    private static final String PARAM_OPERATION = "OPERATION";
+    private static final String PARAM_USERNAME  = "USERNAME";
+    private static final String PARAM_TOKEN     = "TOKEN";
+    private static final String PARAM_SECRET    = "SECRET";
+    private static final String OP_ISSUE        = "issue";
+    private static final String OP_VALIDATE     = "validate";
+    private static final String OP_INVALIDATE   = "invalidate";
 
-    public AliasTokenRestlet(Context context, Request request, Response response) {
+    public AliasTokenRestlet(Context context, Request request, Response response) throws ResourceException {
         super(context, request, response);
         getVariants().add(new Variant(MediaType.APPLICATION_JSON));
         _operation = (String) getRequest().getAttributes().get(PARAM_OPERATION);
@@ -50,6 +49,12 @@ public class AliasTokenRestlet extends SecureResource {
         _token = (String) getRequest().getAttributes().get(PARAM_TOKEN);
         final String secret = (String) getRequest().getAttributes().get(PARAM_SECRET);
         _secret = StringUtils.isBlank(secret) ? INVALID : Long.parseLong(secret);
+
+        _serializer = XDAT.getContextService().getBean(SerializerService.class);
+        if (null == _serializer) {
+            getResponse().setStatus(Status.CLIENT_ERROR_FAILED_DEPENDENCY, "Serializer service was not properly initialized.");
+            throw new ResourceException(Status.CLIENT_ERROR_FAILED_DEPENDENCY, "ERROR: Serializer service was not properly initialized.");
+        }
     }
 
     @Override
@@ -69,9 +74,9 @@ public class AliasTokenRestlet extends SecureResource {
                 throw new ResourceException(Status.CLIENT_ERROR_UNAUTHORIZED, "You must specify both token and secret to validate a token.");
             }
             try {
-                final HashMap<String, String> results = new HashMap<String, String>();
+                final HashMap<String, String> results = new HashMap<>();
                 results.put("valid", getService().validateToken(_token, _secret));
-                return new StringRepresentation(_serializer.writeValueAsString(results));
+                return new StringRepresentation(_serializer.toJson(results));
             } catch (IOException exception) {
                 throw new ResourceException(Status.SERVER_ERROR_INTERNAL, exception.toString());
             }
@@ -89,7 +94,7 @@ public class AliasTokenRestlet extends SecureResource {
         map.put("secret", Long.toString(token.getSecret()));
         String value = "";
         try {
-            value = _serializer.writeValueAsString(map);
+            value = _serializer.toJson(map);
         } catch (IOException e) {
             //
         }
@@ -109,10 +114,10 @@ public class AliasTokenRestlet extends SecureResource {
     }
 
     private static final int INVALID = -1;
-    private static final ObjectMapper _serializer = new ObjectMapper();
-    private AliasTokenService _service;
-    private String _operation;
-    private final String _username;
-    private final String _token;
-    private final long _secret;
+    private final SerializerService _serializer;
+    private       AliasTokenService _service;
+    private       String            _operation;
+    private final String            _username;
+    private final String            _token;
+    private final long              _secret;
 }
diff --git a/src/main/java/org/nrg/xnat/restlet/services/AuditRestlet.java b/src/main/java/org/nrg/xnat/restlet/services/AuditRestlet.java
index f43ed8e12d7864217e09016f8a4e578572584aa4..1c3619ed761d1fd27d200041b623cf2e0a751703 100644
--- a/src/main/java/org/nrg/xnat/restlet/services/AuditRestlet.java
+++ b/src/main/java/org/nrg/xnat/restlet/services/AuditRestlet.java
@@ -20,7 +20,7 @@ import org.nrg.xft.exception.ElementNotFoundException;
 import org.nrg.xft.schema.Wrappers.GenericWrapper.GenericWrapperElement;
 import org.nrg.xft.search.CriteriaCollection;
 import org.nrg.xft.search.ItemSearch;
-import org.nrg.xft.utils.StringUtils;
+import org.nrg.xft.utils.XftStringUtils;
 import org.nrg.xnat.restlet.representations.ItemHTMLRepresentation;
 import org.nrg.xnat.restlet.resources.SecureResource;
 import org.restlet.Context;
@@ -47,7 +47,7 @@ public class AuditRestlet extends SecureResource {
 		key=this.filepath.substring(filepath.indexOf("/")+1);
 		
 
-		List<String> ids=StringUtils.DelimitedStringToArrayList(key, ",");
+		List<String> ids=XftStringUtils.DelimitedStringToArrayList(key, ",");
 		
 		try {
 			item=retrieveItemByIds(xsiType, ids);
diff --git a/src/main/java/org/nrg/xnat/restlet/services/SettingsRestlet.java b/src/main/java/org/nrg/xnat/restlet/services/SettingsRestlet.java
index e1efa228c74e3f15b34358779f3a04025b9b7a7e..5625fdcfef13fe6c9bc871ca79efc491c07652b4 100644
--- a/src/main/java/org/nrg/xnat/restlet/services/SettingsRestlet.java
+++ b/src/main/java/org/nrg/xnat/restlet/services/SettingsRestlet.java
@@ -10,8 +10,6 @@
  */
 package org.nrg.xnat.restlet.services;
 
-import com.fasterxml.jackson.core.JsonFactory;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.apache.commons.lang.StringUtils;
 import org.hibernate.PropertyNotFoundException;
 import org.nrg.config.entities.Configuration;
@@ -66,12 +64,17 @@ public class SettingsRestlet extends SecureResource {
 
     public SettingsRestlet(Context context, Request request, Response response) throws IOException {
         super(context, request, response);
+
         setModifiable(true);
-        this.getVariants().add(new Variant(MediaType.APPLICATION_JSON));
-        this.getVariants().add(new Variant(MediaType.TEXT_XML));
+
+        getVariants().add(new Variant(MediaType.APPLICATION_JSON));
+        getVariants().add(new Variant(MediaType.TEXT_XML));
+
+        _filterService = XDAT.getContextService().getBean(DicomFilterService.class);
 
         _arcSpec = ArcSpecManager.GetInstance();
         _property = (String) getRequest().getAttributes().get("PROPERTY");
+
         if (!StringUtils.isBlank(_property)) {
             if (_property.equals("initialize")) {
                 if (_arcSpec != null && _arcSpec.isComplete()) {
@@ -103,7 +106,7 @@ public class SettingsRestlet extends SecureResource {
             if (StringUtils.isBlank(_property)) {
                 return mediaType == MediaType.TEXT_XML ?
                         new ItemXMLRepresentation(_arcSpec.getItem(), mediaType) :
-                        new StringRepresentation("{\"ResultSet\":{\"Result\":" + new ObjectMapper().writeValueAsString(getArcSpecAsMap()) + ", \"title\": \"Settings\"}}");
+                        new StringRepresentation("{\"ResultSet\":{\"Result\":" + toJson(getArcSpecAsMap()) + ", \"title\": \"Settings\"}}");
             } else {
                 if (!getArcSpecAsMap().containsKey(_property)) {
                     throw new PropertyNotFoundException(String.format("Setting '%s' was not found in the system.", _property));
@@ -114,7 +117,7 @@ public class SettingsRestlet extends SecureResource {
                     String xml = "<" + _property + ">" + propertyValue.toString() + "</" + _property + ">";
                     return new StringRepresentation(xml, mediaType);
                 } else {
-                    return new StringRepresentation("{\"ResultSet\":{\"Result\":" + new ObjectMapper().writeValueAsString(propertyValue) + ", \"title\": \"" + _property + "\"}}");
+                    return new StringRepresentation("{\"ResultSet\":{\"Result\":" + toJson(propertyValue) + ", \"title\": \"" + _property + "\"}}");
                 }
             }
         } catch (PropertyNotFoundException exception) {
@@ -180,11 +183,6 @@ public class SettingsRestlet extends SecureResource {
     }
 
     private DicomFilterService getDicomFilterService() {
-        if (_filterService == null) {
-            synchronized (_log) {
-                _filterService = XDAT.getContextService().getBean(DicomFilterService.class);
-            }
-        }
         return _filterService;
     }
 
@@ -225,7 +223,7 @@ public class SettingsRestlet extends SecureResource {
                 map.put(atoms[0], atoms[1]);
             }
 
-            return MAPPER.writeValueAsString(map);
+            return toJson(map);
         } catch (IOException ignored) {
             // We're not reading from a file, so we shouldn't encounter this.
         }
@@ -234,7 +232,7 @@ public class SettingsRestlet extends SecureResource {
     }
 
     // TODO: Gross.
-    public static final String ADMIN_USERNAME_FOR_SUBSCRIPTION = "admin";
+    private static final String ADMIN_USERNAME_FOR_SUBSCRIPTION = "admin";
 
     /**
      * This returns the current subscriber or subscribers to a particular <i>site-wide</i> event. If the event doesn't
@@ -934,11 +932,9 @@ public class SettingsRestlet extends SecureResource {
         return users.get(0).getLogin();
     }
 
-    private static final ObjectMapper MAPPER = new ObjectMapper(new JsonFactory());
-
     private static final Logger _log = LoggerFactory.getLogger(SettingsRestlet.class);
 
-    private DicomFilterService _filterService;
+    private final DicomFilterService _filterService;
 
     private NotificationService _notificationService;
     private ArcArchivespecification _arcSpec;
diff --git a/src/main/java/org/nrg/xnat/services/ThemeService.java b/src/main/java/org/nrg/xnat/services/ThemeService.java
new file mode 100644
index 0000000000000000000000000000000000000000..47b61271374a434602fb88d3eb13fed31499cae5
--- /dev/null
+++ b/src/main/java/org/nrg/xnat/services/ThemeService.java
@@ -0,0 +1,141 @@
+/*
+ * org.nrg.xnat.turbine.modules.screens.ManageProtocol
+ * XNAT http://www.xnat.org
+ * Copyright (c) 2013, Washington University School of Medicine
+ * All Rights Reserved
+ *
+ * Released under the Simplified BSD.
+ *
+ * Author: Justin Cleveland <clevelandj@wustl.edu> (jcleve01)
+ * Last modified 2/29/2016 11:20 AM
+ */
+package org.nrg.xnat.services;
+
+import org.jetbrains.annotations.NotNull;
+import org.nrg.xnat.configuration.ThemeConfig;
+import org.springframework.stereotype.Service;
+
+import java.io.*;
+import java.util.List;
+
+@Service
+public interface ThemeService {
+
+//    abstract public void postServiceConstruction();
+
+    /**
+     * Returns the system theme file directory for reference by other XNAT modules.
+     * @return the system theme file directory
+     */
+    String getThemesPath();
+
+    /**
+     * Gets the currently selected global system theme from a cache for a specific user role, or secondarily from the theme.json file in the themes folder.
+     * @param role the name of the user role to fetch from the current global theme
+     * @return The currently selected system theme configuration
+     */
+    ThemeConfig getTheme(String role);
+
+    /**
+     * Gets the currently selected global system theme from a cache, or secondarily from the theme.json file in the themes folder.
+     * @return The currently selected global system theme configuration
+     */
+    ThemeConfig getTheme();
+
+    /**
+     * Searches the theme directory if a global theme is applied and returns a path string to the referenced page to redirect to.
+     * If no global theme is selected or no overriding page is found the calling method should continue with it's default XNAT behavior.
+     * @return a path string the referenced page if found. Otherwise returns null.
+     */
+    String getThemePage(String pageName);
+
+    /**
+     * Searches the theme directory if a global theme is applied and returns a path string to the referenced theme and matching type to redirect to.
+     * If no global theme is selected or no overriding page with specified type is found the calling method should continue with it's default XNAT behavior.
+     * @return a path string the referenced theme and type if found. Otherwise returns null.
+     */
+    String getThemePage(String pageName, String type);
+
+    /**
+     * Sets the currently selected system theme in the theme.json file in the web application's themes folder and caches it.
+     * @param themeConfig the theme configuration object to apply
+     */
+    ThemeConfig setTheme(ThemeConfig themeConfig) throws ThemeNotFoundException;
+
+    /**
+     * Sets the currently selected system theme in the theme.json file in the web application's themes folder and caches it.
+     * @param name the theme name. Creates a theme configuration object with it applying defaults
+     */
+    ThemeConfig setTheme(String name) throws ThemeNotFoundException;
+
+    /**
+     * Sets the currently selected system theme in the theme.json file in the web application's themes folder and caches it.
+     * Creates a theme configuration object with it applying a defaults path
+     * @param name the theme name.
+     * @param enabled flag specifying whether or not the theme should be active.
+     */
+    ThemeConfig setTheme(String name, boolean enabled) throws ThemeNotFoundException;
+
+    /**
+     * Sets the currently selected system theme in the theme.json file in the web application's themes folder and caches it.
+     * @param name the theme name.
+     * @param path base theme directory path.
+     * @param enabled flag specifying whether or not the theme should be active.
+     */
+    ThemeConfig setTheme(String name, String path, boolean enabled) throws ThemeNotFoundException;
+
+    /**
+     * Loads the system theme options
+     * @return The list of the available theme packages (folder names) available under the system themes directory
+     */
+    List<TypeOption> loadExistingThemes();
+
+    /**
+     * Checks if the specified theme exists.
+     * @param name the name of the theme to look for
+     * @return true if it could be found in the system theme directory
+     */
+    boolean themeExists(String name);
+
+    /**
+     * Extracts a zipped theme package from an given InputStream.
+     * @param inputStream from which to read the zipped data
+     * @return List of root level directories (theme names) that were extracted
+     * @throws IOException
+     */
+    List<String> extractTheme(InputStream inputStream) throws IOException;
+
+    /**
+     * Helper exception to report more specific errors
+     */
+    class ThemeNotFoundException extends Exception{
+        private String invalidTheme;
+        public ThemeNotFoundException(String invalidTheme) {
+            this.invalidTheme = invalidTheme;
+        }
+        public String getInvalidTheme() {
+            return invalidTheme;
+        }
+    }
+
+    /**
+     * Helper class to organize the available themes for display in a select dropdown form
+     */
+    class TypeOption implements Comparable<TypeOption> {
+        String value, label;
+        public TypeOption(String value, String label) {
+            this.value = value;
+            this.label = label;
+        }
+        public String getValue() {
+            return value;
+        }
+        public String getLabel() {
+            return label;
+        }
+        @Override
+        public int compareTo(@NotNull TypeOption that) {
+            return this.label.compareToIgnoreCase(that.label);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/nrg/xnat/services/impl/ThemeServiceImpl.java b/src/main/java/org/nrg/xnat/services/impl/ThemeServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..8f2d1315f6879cf53f9ad0c6562d9c92f3401f8e
--- /dev/null
+++ b/src/main/java/org/nrg/xnat/services/impl/ThemeServiceImpl.java
@@ -0,0 +1,325 @@
+/*
+ * org.nrg.xnat.turbine.modules.screens.ManageProtocol
+ * XNAT http://www.xnat.org
+ * Copyright (c) 2013, Washington University School of Medicine
+ * All Rights Reserved
+ *
+ * Released under the Simplified BSD.
+ *
+ * Author: Justin Cleveland <clevelandj@wustl.edu> (jcleve01)
+ * Last modified 2/30/2016 11:20 AM
+ */
+package org.nrg.xnat.services.impl;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import org.apache.commons.lang.ArrayUtils;
+import org.apache.commons.lang.StringUtils;
+import org.nrg.xnat.configuration.ThemeConfig;
+import org.nrg.xnat.services.ThemeService;
+import org.nrg.xnat.utils.SerializerService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import javax.servlet.ServletContext;
+import java.io.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+@Service
+public class ThemeServiceImpl implements ThemeService {
+    private static String webRelativeThemePath="themes";
+    private static String themesPath;
+    private static ThemeConfig themeConfig = null;
+    private static File themeFile = null;
+    private static final int FILE_BUFFER_SIZE = 4096;
+
+    @Autowired
+    private SerializerService _serializer;
+
+    @Autowired
+    private ServletContext servletContext;
+
+    @PostConstruct
+    public void postServiceConstruction(){
+        themesPath = servletContext.getRealPath(File.separator)+webRelativeThemePath;
+        themeFile = new File(themesPath + File.separator + "theme.json");
+        File checkThemesPath = new File(themesPath);
+        if (!checkThemesPath.exists()) {
+            checkThemesPath.mkdir();
+        }
+System.out.println("Theme Path: "+themeFile);
+        servletContext.setAttribute("ThemeService", this);  // This is probably a terrible way to attempt to do this. We would ideally add an instance of ThemeService to the XDAT class
+    }
+
+    public String getThemesPath() {
+        return themesPath;
+    }
+
+    /**
+     * Gets the currently selected system theme from an application servlet context cache, or secondarily from the
+     * theme.json file in the themes folder.
+     * @return The currently selected system theme configuration
+     */
+    public ThemeConfig getTheme(String role) {
+        if(themeConfig != null){
+            return themeConfig;
+        } else {                        // Read the last saved theme selection from the theme.json file in the themes
+            if (themeFile.exists()) {   // directory in the event it can't be found in the application context.
+                try {                   // (ie. the server was just started/restarted)
+                    BufferedReader reader = new BufferedReader(new FileReader(themeFile));
+                    StringBuilder sb = new StringBuilder();
+                    String line;
+                    while ((line = reader.readLine()) != null) {
+                        sb.append(line).append("\n");
+                    }
+                    reader.close();
+                    String contents = sb.toString();
+                    themeConfig = _serializer.deserializeJson(contents, ThemeConfig.class);
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            try {
+                setTheme(themeConfig);
+            } catch (ThemeNotFoundException e) {
+                e.printStackTrace();
+            }
+        }
+        if (role != null) {
+            // TODO: implement search through the roles array in the ThemeConfig object for a matching ThemeConfig object for the specified role
+        }
+        return themeConfig;
+    }
+    public ThemeConfig getTheme() {
+        return getTheme(null);
+    }
+
+    /**
+     * Searches the theme directory if a global theme is applied and returns a path string to the referenced page to redirect to.
+     * If no global theme is selected or no overriding page is found the calling method should continue with it's default XNAT behavior.
+     * @return a path string the referenced page if found. Otherwise returns null.
+     */
+    public String getThemePage(String pageName) {
+        return getThemePage(pageName, null);
+    }
+
+    /**
+     * Searches the theme directory if a global theme is applied and returns a path string to the referenced theme and matching type to redirect to.
+     * If no global theme is selected or no overriding page with specified type is found the calling method should continue with it's default XNAT behavior.
+     * @return a path string the referenced theme and type if found. Otherwise returns null.
+     */
+    public String getThemePage(String pageName, String type) {
+        String pagePath;
+        ThemeConfig theme = getTheme();
+        if(theme == null){
+            return null;
+        } else if (pageName == null){
+            return null;
+        } else { // Read the last saved theme selection from the theme.json file in the themes
+            pagePath = checkThemeFileExists(theme, pageName, type);
+        }
+        return pagePath;
+    }
+
+    /**
+     * Checks for the existence of a file name with a given set of accepted file extensions in the theme directory
+     * and returns a relative web path string the referenced page if found.
+     * @return a relative web path string prioritized by extension to the referenced page if found. Otherwise returns null.
+     */
+    private String checkThemeFileExists(ThemeConfig theme, String pageName) {
+        return checkThemeFileExists(theme, pageName, null);
+    }
+
+    private String checkThemeFileExists(ThemeConfig theme, String pageName, String type) {
+        String pagePath = null, typeSep = type + "s" + File.separator;
+        String[] extensions = new String[]{};
+        String[] pageExts = new String[]{"jsp", "vm", "htm", "html"};
+        String[] scriptExts = new String[]{"js"};
+        String[] styleExts = new String[]{"css"};
+        if("page".equals(type)){
+            extensions = (String[]) ArrayUtils.addAll(extensions, pageExts);
+        }
+        if("script".equals(type)){
+            extensions = (String[]) ArrayUtils.addAll(extensions, scriptExts);
+        }
+        if("style".equals(type)){
+            extensions = (String[]) ArrayUtils.addAll(extensions, styleExts);
+        }
+        if(type == null){
+            typeSep = "";
+            extensions = (String[]) ArrayUtils.addAll(extensions, pageExts);
+            extensions = (String[]) ArrayUtils.addAll(extensions, scriptExts);
+            extensions = (String[]) ArrayUtils.addAll(extensions, styleExts);
+        }
+        for (String ext : extensions) {
+            File themePageFile = new File(theme.getPath() + File.separator + typeSep + pageName + "." + ext);
+            if(themePageFile.exists()) {
+                if(type != null){
+                    typeSep = type + "s/";  // This is awful and should be set once up above
+                }
+                pagePath = "/" + webRelativeThemePath + "/" + theme.getName() + "/" + typeSep + pageName + "." + ext;
+                break;
+            }
+        }
+        return pagePath;
+    }
+
+    /**
+     * Sets the currently selected system theme in the theme.json file in the web application's themes folder and caches it.
+     * @param themeConfig the theme configuration object to apply
+     */
+    public ThemeConfig setTheme(ThemeConfig themeConfig) throws ThemeNotFoundException {
+        try {
+            if (themeConfig == null){
+                themeConfig = new ThemeConfig();
+            }
+            if(themeExists(themeConfig.getName())) {
+                String themeJson = _serializer.toJson(themeConfig);
+                if (!themeFile.exists()) {
+                    themeFile.createNewFile();
+                }
+                FileWriter writer = new FileWriter(themeFile);
+                writer.write(themeJson);
+                writer.flush();
+                writer.close();
+                ThemeServiceImpl.themeConfig = themeConfig;
+            } else {
+                throw new ThemeNotFoundException(themeConfig.getName());
+            }
+        } catch (JsonProcessingException e) {
+            e.printStackTrace();
+            // TODO: rethrow this and respond as an internal server error
+        } catch (IOException e) {
+            e.printStackTrace();
+            // TODO: rethrow this and respond as an internal server error
+        }
+        return themeConfig;
+    }
+
+    /**
+     * Sets the currently selected system theme in the theme.json file in the web application's themes folder and caches it.
+     * @param name the theme name. Creates a theme configuration object with it applying defaults
+     */
+    public ThemeConfig setTheme(String name) throws ThemeNotFoundException {
+        return setTheme(name, true);
+    }
+
+    /**
+     * Sets the currently selected system theme in the theme.json file in the web application's themes folder and caches it.
+     * Creates a theme configuration object with it applying a defaults path
+     * @param name the theme name.
+     * @param enabled flag specifying whether or not the theme should be active.
+     */
+    public ThemeConfig setTheme(String name, boolean enabled) throws ThemeNotFoundException {
+        return setTheme(new ThemeConfig(name, themesPath + File.separator + name, enabled));
+    }
+
+    /**
+     * Sets the currently selected system theme in the theme.json file in the web application's themes folder and caches it.
+     * @param name the theme name.
+     * @param path base theme directory path.
+     * @param enabled flag specifying whether or not the theme should be active.
+     */
+    public ThemeConfig setTheme(String name, String path, boolean enabled) throws ThemeNotFoundException {
+        return setTheme(new ThemeConfig(name, path, enabled));
+    }
+
+    /**
+     * Loads the system theme options
+     * @return The list of the available theme packages (folder names) available under the system themes directory
+     */
+    public List<TypeOption> loadExistingThemes() {
+        ArrayList<TypeOption> themeOptions = new ArrayList<>();
+        themeOptions.add(new TypeOption(null, "None"));
+        File f = new File(themesPath); // current directory
+        FileFilter directoryFilter = new FileFilter() {
+            public boolean accept(File file) {
+                return file.isDirectory();
+            }
+        };
+        File[] files = f.listFiles(directoryFilter);
+        if(files != null) {
+            for (File file : files) {
+                if (file.isDirectory()) {
+                    themeOptions.add(new TypeOption(file.getName(), file.getName()));
+                }
+            }
+        }
+        return themeOptions;
+    }
+
+    /**
+     * Checks if the specified theme exists.
+     * @param name the name of the theme to look for
+     * @return true if it could be found in the system theme directory
+     */
+    public boolean themeExists(String name) {
+        if(name == null) {
+            return true;
+        } else if(StringUtils.isEmpty(name)){
+            return false;
+        } else {
+            List<TypeOption> themeList = loadExistingThemes();
+            for (TypeOption to: themeList) {
+                if(name.equals(to.getValue())){
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Extracts a zipped theme package from an given InputStream.
+     * @param inputStream from which to read the zipped data
+     * @return List of root level directories (theme names) that were extracted
+     * @throws IOException
+     */
+    public List<String> extractTheme(InputStream inputStream) throws IOException {
+        final List<String> rootDirs = new ArrayList<>();
+        ZipInputStream zipIn = new ZipInputStream(inputStream);
+        ZipEntry entry = zipIn.getNextEntry();
+        while (entry != null) {  // iterate over entries in the zip file
+            String filePath = this.getThemesPath() + File.separator + entry.getName();
+            if (!entry.isDirectory()) {  // if the entry is a file, extract it      // TODO: Make sure we get a directory the first iteration through (fail otherwise) so that no files get dumped in the root themes directory
+                this.extractFile(zipIn, filePath);
+            } else {  // if the entry is a directory, make the directory
+                String rootDir = entry.getName();
+                int slashIndex = rootDir.indexOf('/');
+                if(slashIndex>1){
+                    int nextSlashIndex = rootDir.indexOf('/', slashIndex+1);
+                    if(nextSlashIndex<0) {
+                        rootDir = rootDir.substring(0, slashIndex);
+                        rootDirs.add(rootDir);
+                    }
+                }
+                File dir = new File(filePath);
+                dir.mkdir();
+            }
+            zipIn.closeEntry();
+            entry = zipIn.getNextEntry();
+        }
+        zipIn.close();
+        inputStream.close();
+        return rootDirs;
+    }
+
+    /**
+     * Extracts a single zip entry (file entry)
+     * @param zip zip input stream to extract it from
+     * @param path to the file within the zip package
+     * @throws IOException
+     */
+    private void extractFile(ZipInputStream zip, String path) throws IOException {
+        BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(path));
+        byte[] bytes = new byte[FILE_BUFFER_SIZE];
+        int length;
+        while ((length = zip.read(bytes)) != -1) {
+            os.write(bytes, 0, length);
+        }
+        os.close();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/nrg/xnat/spawner/controllers/ManageElementsController.java b/src/main/java/org/nrg/xnat/spawner/controllers/ManageElementsController.java
new file mode 100644
index 0000000000000000000000000000000000000000..e806046f2010b4aecb9d9785dc8facdeb5896c34
--- /dev/null
+++ b/src/main/java/org/nrg/xnat/spawner/controllers/ManageElementsController.java
@@ -0,0 +1,61 @@
+package org.nrg.xnat.spawner.controllers;
+
+import org.apache.commons.lang3.StringUtils;
+import org.nrg.xnat.spawner.entities.SpawnerElement;
+import org.nrg.xnat.spawner.exceptions.InvalidElementIdException;
+import org.nrg.xnat.spawner.services.SpawnerService;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.servlet.ModelAndView;
+
+import javax.inject.Inject;
+import java.util.List;
+
+@SuppressWarnings("SpringMVCViewInspection")
+@Controller
+@RequestMapping(value = "/spawner/elements", produces = "application/json")
+public class ManageElementsController {
+
+    @RequestMapping
+    public ModelAndView getAvailableElements() {
+        final List<SpawnerElement> elements = _service.getAll();
+        return new ModelAndView("spawner/elements", "elements", elements);
+    }
+
+    @RequestMapping(value = "{elementId}", method = RequestMethod.GET)
+    public ModelAndView getElement(@PathVariable final String elementId) {
+        final SpawnerElement element = _service.retrieve(elementId);
+        return new ModelAndView("spawner/element", element == null ? "error" : "elementId", element == null ? "The ID element " + elementId + " was not found in the system." : elementId);
+    }
+
+    @RequestMapping(value = "{elementId}", method = RequestMethod.PUT)
+    public ModelAndView setElement(@PathVariable final String elementId, @RequestBody final SpawnerElement element) {
+        if (element == null) {
+            return new ModelAndView("spawner/element", "error", "No valid spawner element was found in your submitted data.");
+        }
+        final SpawnerElement existing       = _service.retrieve(elementId);
+        final boolean        isModElementId = !StringUtils.equals(existing.getElementId(), element.getElementId());
+        final boolean        isModYaml      = !StringUtils.equals(existing.getYaml(), element.getYaml());
+        if (isModElementId || isModYaml) {
+            if (isModElementId) {
+                try {
+                    existing.setElementId(element.getElementId());
+                } catch (InvalidElementIdException e) {
+                    return new ModelAndView("spawner/element", "error", "The element ID " + element.getElementId() + " in your submitted data is invalid. Check for duplicated or invalid values.");
+                }
+            }
+            if (isModYaml) {
+                existing.setYaml(element.getYaml());
+            }
+            _service.update(existing);
+            return new ModelAndView("spawner/element", "element", element);
+        }
+        return new ModelAndView("spawner/element", "error", "The submitted spawner element wasn't modified, not updated.");
+    }
+
+    @Inject
+    private SpawnerService _service;
+}
diff --git a/src/main/java/org/nrg/xnat/turbine/modules/actions/ManageProjectAccess.java b/src/main/java/org/nrg/xnat/turbine/modules/actions/ManageProjectAccess.java
index 1353358cc214460076ab25dccda276f0a132693d..b6e41c4b1254751014d357458303080824ff2aec 100644
--- a/src/main/java/org/nrg/xnat/turbine/modules/actions/ManageProjectAccess.java
+++ b/src/main/java/org/nrg/xnat/turbine/modules/actions/ManageProjectAccess.java
@@ -27,7 +27,7 @@ import org.nrg.xft.event.persist.PersistentWorkflowUtils;
 import org.nrg.xft.exception.InvalidPermissionException;
 import org.nrg.xft.exception.ItemNotFoundException;
 import org.nrg.xft.security.UserI;
-import org.nrg.xft.utils.StringUtils;
+import org.nrg.xft.utils.XftStringUtils;
 import org.nrg.xnat.utils.WorkflowUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -67,9 +67,9 @@ public class ManageProjectAccess extends SecureAction {
         String members = ((String) org.nrg.xdat.turbine.utils.TurbineUtils.GetPassedParameter("members", data));
         String owners = ((String) org.nrg.xdat.turbine.utils.TurbineUtils.GetPassedParameter("owners", data));
 
-        List<String> ownersL = StringUtils.CommaDelimitedStringToArrayList(owners);
-        List<String> membersL = StringUtils.CommaDelimitedStringToArrayList(members);
-        List<String> collaboratorsL = StringUtils.CommaDelimitedStringToArrayList(collaborators);
+        List<String> ownersL = XftStringUtils.CommaDelimitedStringToArrayList(owners);
+        List<String> membersL = XftStringUtils.CommaDelimitedStringToArrayList(members);
+        List<String> collaboratorsL = XftStringUtils.CommaDelimitedStringToArrayList(collaborators);
 
 
         if (owners != null) {
diff --git a/src/main/java/org/nrg/xnat/turbine/modules/actions/QuickSearchAction.java b/src/main/java/org/nrg/xnat/turbine/modules/actions/QuickSearchAction.java
index 278a048478ecb10d97956ae9094356c53ccd850f..b51fc6ccb3a73897c77e3e1e6d9e264b607f0943 100644
--- a/src/main/java/org/nrg/xnat/turbine/modules/actions/QuickSearchAction.java
+++ b/src/main/java/org/nrg/xnat/turbine/modules/actions/QuickSearchAction.java
@@ -16,6 +16,7 @@ import java.util.Hashtable;
 import java.util.List;
 import java.util.Map;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 import org.apache.turbine.util.RunData;
 import org.apache.velocity.context.Context;
@@ -39,7 +40,7 @@ import org.nrg.xft.exception.FieldNotFoundException;
 import org.nrg.xft.exception.XFTInitException;
 import org.nrg.xft.search.TableSearch;
 import org.nrg.xft.security.UserI;
-import org.nrg.xft.utils.StringUtils;
+import org.nrg.xft.utils.XftStringUtils;
 
 import com.google.common.collect.Lists;
 
@@ -202,7 +203,7 @@ public class QuickSearchAction extends SecureAction {
                                 
                                 String identifier = idSet.substring(0,idSet.indexOf(" ("));
                                 idSet = idSet.substring(idSet.indexOf(" (")+2);
-                                ArrayList<String> ps = StringUtils.CommaDelimitedStringToArrayList(idSet, true);
+                                ArrayList<String> ps = XftStringUtils.CommaDelimitedStringToArrayList(idSet, true);
                                 
                                 for (String p : ps){
                                 	if(Permissions.canRead(user,element_name + "/sharing/share/project", p)){
@@ -313,10 +314,10 @@ public class QuickSearchAction extends SecureAction {
                     boolean canRead=false;
                     for(Integer pC: columns){
                         String pId=(String)t.rows().get(rowC)[pC];
-                        if(!StringUtils.IsEmpty(pId)){
+                        if(StringUtils.isNotBlank(pId)){
                             String[] projects=pId.split(", ");
                             for(String project:projects){
-                                if(!StringUtils.IsEmpty(project)){
+                                if(StringUtils.isNotBlank(project)){
                                     project=project.replace(">", "");
                                     project=project.replace("<", "");
                                     if(Permissions.canRead(user,xsiPath,project)){
diff --git a/src/main/java/org/nrg/xnat/turbine/modules/screens/ArcGet.java b/src/main/java/org/nrg/xnat/turbine/modules/screens/ArcGet.java
index e679a17b3867a970ff229da535b6198eb8cce436..98fc5500760ace665ecb09b2cd4e4a0708749f50 100644
--- a/src/main/java/org/nrg/xnat/turbine/modules/screens/ArcGet.java
+++ b/src/main/java/org/nrg/xnat/turbine/modules/screens/ArcGet.java
@@ -25,6 +25,7 @@ import java.util.zip.ZipOutputStream;
 
 import javax.servlet.http.HttpServletResponse;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 import org.apache.turbine.util.RunData;
 import org.nrg.xdat.model.XnatImagescandataI;
@@ -42,7 +43,7 @@ import org.nrg.xft.exception.FieldNotFoundException;
 import org.nrg.xft.exception.XFTInitException;
 import org.nrg.xft.security.UserI;
 import org.nrg.xft.utils.FileUtils;
-import org.nrg.xft.utils.StringUtils;
+import org.nrg.xft.utils.XftStringUtils;
 import org.nrg.xft.utils.zip.ZipI;
 import org.nrg.xft.utils.zip.ZipUtils;
 import org.nrg.xnat.srb.XNATDirectory;
@@ -163,7 +164,7 @@ created in buildPDF.
                                                 Hashtable fileGroups = mr.getFileGroups();
                                                 for (Enumeration e = fileGroups.keys(); e.hasMoreElements();) {
                                                     String key = (String)e.nextElement();
-                                                    if (key.toLowerCase().indexOf("scan")!=-1){
+                                                    if (key.toLowerCase().contains("scan")){
                                                         XNATDirectory filesA = (XNATDirectory)fileGroups.get(key);
                                                         zip.write(filesA);
                                                     }
@@ -171,7 +172,7 @@ created in buildPDF.
                                             }else{
                                                 Hashtable fileGroups = mr.getFileGroups();
                                                 raw = raw.trim();
-                                                ArrayList rawTypes = StringUtils.CommaDelimitedStringToArrayList(raw);
+                                                ArrayList rawTypes = XftStringUtils.CommaDelimitedStringToArrayList(raw);
                                                 Iterator iter= rawTypes.iterator();
                                                 while (iter.hasNext())
                                                 {
@@ -180,7 +181,7 @@ created in buildPDF.
                                                     if (scans!=null && scans.size()>0)
                                                     {
                                                         for(XnatImagescandata scan: scans){
-                                                            String parsedScanID= StringUtils.ReplaceStr(StringUtils.ReplaceStr(scan.getId(),"-",""),"*","AST");
+                                                            String parsedScanID= StringUtils.replace(StringUtils.replace(scan.getId(), "-", ""), "*", "AST");
                                                             XNATDirectory filesA = (XNATDirectory)fileGroups.get("scan" +parsedScanID);
                                                             zip.write(filesA);
 
@@ -208,13 +209,13 @@ created in buildPDF.
                                             } else {
                                         	Hashtable fileGroups = mr.getFileGroups();
                                         	processed = processed.trim();
-                                        	for (final String rType : StringUtils.CommaDelimitedStringToArrayList(processed)) {
+                                        	for (final String rType : XftStringUtils.CommaDelimitedStringToArrayList(processed)) {
                                         	    Collection<XnatReconstructedimagedata> scans= mr.getReconstructionsByType(rType);
                                         	    if (scans.isEmpty()) {
                                         		fw.write("No " +rType + " Processed Images Found.\n");
                                         	    } else {
                                         		for (final XnatReconstructedimagedata scan : scans) {
-                                        		    final String parsedScanID= StringUtils.ReplaceStr(StringUtils.ReplaceStr(scan.getId(),"-",""),"*","AST");
+                                        		    final String parsedScanID= StringUtils.replace(StringUtils.replace(scan.getId(),"-",""),"*","AST");
                                         		    final XNATDirectory filesA = (XNATDirectory)fileGroups.get("recon" +parsedScanID);
                                         		    zip.write(filesA);
                                         		    fw.write("Including " +filesA.getSize() + " Processed Files for " + scan.getId() +" (" + rType + ").\n");                                                	    
@@ -290,7 +291,7 @@ created in buildPDF.
                                                                 {
                                                                     include=true;
                                                                 }else{
-                                                                    ArrayList qualities = StringUtils.CommaDelimitedStringToArrayList(quality);
+                                                                    ArrayList qualities = XftStringUtils.CommaDelimitedStringToArrayList(quality);
                                                                     if (scan.getQuality()!=null){
                                                                         if (qualities.contains(scan.getQuality()))
                                                                         {
@@ -301,7 +302,7 @@ created in buildPDF.
                                                             }
                                                             
                                                             if (include){
-                                                                String parsedScanID= StringUtils.ReplaceStr(StringUtils.ReplaceStr(scan.getId(),"-",""),"*","AST");
+                                                                String parsedScanID= StringUtils.replace(StringUtils.replace(scan.getId(),"-",""),"*","AST");
                                                                 ArrayList filesA = (ArrayList)fileGroups.get("scan" +parsedScanID);
                                                                 images.addAll(filesA);
                                                                 fw.write("Including " +filesA.size() + " Raw Files for scan " + scan.getId() +" (" + scan.getQuality() + ").\n");
@@ -311,7 +312,7 @@ created in buildPDF.
                                                 }else{
                                                     Hashtable fileGroups = mr.getFileGroups();
                                                     raw = raw.trim();
-                                                    for (final String rType : StringUtils.CommaDelimitedStringToArrayList(raw)) {
+                                                    for (final String rType : XftStringUtils.CommaDelimitedStringToArrayList(raw)) {
                                                         ArrayList<XnatImagescandata> scans= mr.getScansByType(rType);
                                                         if (scans!=null && scans.size()>0)
                                                         {
@@ -325,7 +326,7 @@ created in buildPDF.
                                                                     {
                                                                         include=true;
                                                                     }else{
-                                                                        ArrayList qualities = StringUtils.CommaDelimitedStringToArrayList(quality);
+                                                                        ArrayList qualities = XftStringUtils.CommaDelimitedStringToArrayList(quality);
                                                                         if (scan.getQuality()!=null){
                                                                             if (qualities.contains(scan.getQuality()))
                                                                             {
@@ -336,7 +337,7 @@ created in buildPDF.
                                                                 }
                                                                 
                                                                 if (include){
-                                                                    String parsedScanID= StringUtils.ReplaceStr(StringUtils.ReplaceStr(scan.getId(),"-",""),"*","AST");
+                                                                    String parsedScanID= StringUtils.replace(StringUtils.replace(scan.getId(),"-",""),"*","AST");
                                                                     ArrayList filesA = (ArrayList)fileGroups.get("scan" +parsedScanID);
                                                                     images.addAll(filesA);
                                                                     fw.write("Including " +filesA.size() + " Raw Files for " + scan.getId() +" (" + rType + ":" + scan.getQuality() + ").\n");
@@ -347,7 +348,7 @@ created in buildPDF.
                                                             if (scan==null)
                                                                 fw.write("No " +rType + " Raw Scans Found.\n");
                                                             else{
-                                                                String parsedScanID= StringUtils.ReplaceStr(StringUtils.ReplaceStr(scan.getId(),"-",""),"*","AST");
+                                                                String parsedScanID= StringUtils.replace(StringUtils.replace(scan.getId(),"-",""),"*","AST");
                                                                 ArrayList filesA = (ArrayList)fileGroups.get("scan" +parsedScanID);
                                                                 images.addAll(filesA);
                                                                 fw.write("Including " +filesA.size() + " Raw Files for " + scan.getId() +" (" + rType + ":" + scan.getQuality() + ").\n");
@@ -371,12 +372,12 @@ created in buildPDF.
                                                 }else{
                                                     Hashtable fileGroups = mr.getFileGroups();
                                                     processed = processed.trim();
-                                                    for (final String rType : StringUtils.CommaDelimitedStringToArrayList(processed)) {
+                                                    for (final String rType : XftStringUtils.CommaDelimitedStringToArrayList(processed)) {
                                                         Collection<XnatReconstructedimagedata> scans= mr.getReconstructionsByType(rType);
                                                         if (scans.isEmpty())
                                                         {
                                                             for (final XnatReconstructedimagedata scan : scans) {
-                                                                String parsedScanID= StringUtils.ReplaceStr(StringUtils.ReplaceStr(scan.getId(),"-",""),"*","AST");
+                                                                String parsedScanID= StringUtils.replace(StringUtils.replace(scan.getId(),"-",""),"*","AST");
                                                                 Collection filesA = (Collection)fileGroups.get("recon" +parsedScanID);
                                                                 images.addAll(filesA);    
                                                                 fw.write("Including " +filesA.size() + " Processed Files for " + scan.getId() +" (" + rType + ").\n");
@@ -386,7 +387,7 @@ created in buildPDF.
                                                             if (scan==null)
                                                                 fw.write("No " +rType + " Processed Images Found.\n");
                                                             else{
-                                                                String parsedScanID= StringUtils.ReplaceStr(StringUtils.ReplaceStr(scan.getId(),"-",""),"*","AST");
+                                                                String parsedScanID= StringUtils.replace(StringUtils.replace(scan.getId(),"-",""),"*","AST");
                                                                 ArrayList filesA = (ArrayList)fileGroups.get("recon" +parsedScanID);
                                                                 images.addAll(filesA);
                                                                 fw.write("Including " +filesA.size() + " Processed Files for " + scan.getId() +" (" + rType + ").\n");
diff --git a/src/main/java/org/nrg/xnat/turbine/modules/screens/EditImageAssessorScreen.java b/src/main/java/org/nrg/xnat/turbine/modules/screens/EditImageAssessorScreen.java
index 7c038a42f4d9d3767befa906495ae29ff7336586..f0fcf42476b3a005dae63705b89ba4b80f7fb3aa 100644
--- a/src/main/java/org/nrg/xnat/turbine/modules/screens/EditImageAssessorScreen.java
+++ b/src/main/java/org/nrg/xnat/turbine/modules/screens/EditImageAssessorScreen.java
@@ -10,6 +10,7 @@
  */
 package org.nrg.xnat.turbine.modules.screens;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.turbine.util.RunData;
 import org.nrg.xdat.base.BaseElement;
 import org.nrg.xdat.om.XnatExperimentdata;
@@ -21,7 +22,6 @@ import org.nrg.xft.ItemI;
 import org.nrg.xft.XFTItem;
 import org.nrg.xft.schema.Wrappers.GenericWrapper.GenericWrapperElement;
 import org.nrg.xft.security.UserI;
-import org.nrg.xft.utils.StringUtils;
 
 public abstract class EditImageAssessorScreen extends EditScreenA {
 
@@ -30,11 +30,11 @@ public abstract class EditImageAssessorScreen extends EditScreenA {
 		final UserI user = TurbineUtils.getUser(data);
 		final XnatImageassessordata assessor = (XnatImageassessordata)BaseElement.GetGeneratedItem(XFTItem.NewItem(getElementName(), user));
 		final String search_element = TurbineUtils.GetSearchElement(data);
-		if (!StringUtils.IsEmpty(search_element)) {
+		if (StringUtils.isNotBlank(search_element)) {
 			final GenericWrapperElement se = GenericWrapperElement.GetElement(search_element);
 			if (se.instanceOf(XnatImagesessiondata.SCHEMA_ELEMENT_NAME)) {
 				final String search_value = ((String)org.nrg.xdat.turbine.utils.TurbineUtils.GetPassedParameter("search_value",data));
-				if (!StringUtils.IsEmpty(search_value)) {
+				if (StringUtils.isNotBlank(search_value)) {
 					final XnatImagesessiondata imageSession = new XnatImagesessiondata(TurbineUtils.GetItemBySearch(data));
 
 					// set defaults for new qc assessors
diff --git a/src/main/java/org/nrg/xnat/turbine/modules/screens/Index.java b/src/main/java/org/nrg/xnat/turbine/modules/screens/Index.java
index b8d89bc6bd3e02e91753ea503eec6c56d2c84177..64953b9ed8e8195772189a7d17afba7b9dd3c21a 100644
--- a/src/main/java/org/nrg/xnat/turbine/modules/screens/Index.java
+++ b/src/main/java/org/nrg/xnat/turbine/modules/screens/Index.java
@@ -14,19 +14,41 @@ import java.util.Date;
 
 import org.apache.turbine.util.RunData;
 import org.apache.velocity.context.Context;
+import org.nrg.xdat.XDAT;
 import org.nrg.xdat.security.helpers.UserHelper;
 import org.nrg.xdat.turbine.modules.screens.SecureScreen;
 import org.nrg.xdat.turbine.utils.TurbineUtils;
 import org.nrg.xft.db.PoolDBUtils;
 import org.nrg.xft.security.UserI;
 import org.nrg.xnat.helpers.prearchive.PrearcDatabase;
+
+import org.nrg.xnat.services.ThemeService;
 import org.nrg.xnat.turbine.utils.ProjectAccessRequest;
 
 public class Index extends SecureScreen {
 
     @Override
     protected void doBuildTemplate(RunData data, Context context) throws Exception {
-        
+        ThemeService themeService = XDAT.getContextService().getBean(ThemeService.class);
+//        String themedLandingPath = themeService.getThemePage("Landing");
+//        if(themedLandingPath != null) {
+//            doRedirect(data, themedLandingPath);
+//            data.setRedirectURI(themedLandingPath);
+//        }
+        String themedRedirect = themeService.getThemePage("Landing");           // put all this in a method in the theme service with an optional requested page parameter
+        if(themedRedirect != null) {
+            context.put("themedRedirect", themedRedirect);
+            return;
+        }
+        String themedStyle = themeService.getThemePage("theme", "style");
+        if(themedStyle != null) {
+            context.put("themedStyle", themedStyle);
+        }
+        String themedScript = themeService.getThemePage("theme", "script");
+        if(themedScript != null) {
+            context.put("themedScript", themedScript);
+        }
+
         UserI user = TurbineUtils.getUser(data);
         
         if(((String)org.nrg.xdat.turbine.utils.TurbineUtils.GetPassedParameter("node",data))!=null){
diff --git a/src/main/java/org/nrg/xnat/turbine/modules/screens/LaunchUploadApplet.java b/src/main/java/org/nrg/xnat/turbine/modules/screens/LaunchUploadApplet.java
index 2e97b3d45b8a9cf975c0a0430d26c3368aeb11bf..739fb9436059b98b2fd1dea8f28ab38c1d01df2c 100644
--- a/src/main/java/org/nrg/xnat/turbine/modules/screens/LaunchUploadApplet.java
+++ b/src/main/java/org/nrg/xnat/turbine/modules/screens/LaunchUploadApplet.java
@@ -15,7 +15,6 @@ import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.turbine.util.RunData;
 import org.apache.velocity.context.Context;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.nrg.framework.utilities.Reflection;
 import org.nrg.xdat.om.XnatPvisitdata;
 import org.nrg.xdat.turbine.utils.TurbineUtils;
@@ -28,24 +27,25 @@ import java.util.List;
  * @author timo
  *
  */
+@SuppressWarnings({"WeakerAccess", "unused"})
 public class LaunchUploadApplet extends UploadAppletScreen {
 	
 	private static final Log _log = LogFactory.getLog(LaunchUploadApplet.class);
-	
 
-	
-	@Override
+    @Override
 	public void doBuildTemplate(RunData data, Context context) {
 		context.put("jsessionid", XnatHttpUtils.getJSESSIONID(data));
 		
-		if(StringUtils.trimToEmpty((String)org.nrg.xdat.turbine.utils.TurbineUtils.GetPassedParameter("search_field",data)).equals("xnat:subjectData.ID")) {
-		    context.put("subject", StringUtils.trimToEmpty((String)org.nrg.xdat.turbine.utils.TurbineUtils.GetPassedParameter("search_value",data)));
+		if(StringUtils.trimToEmpty((String)TurbineUtils.GetPassedParameter("search_field",data)).equals("xnat:subjectData.ID")) {
+		    context.put("subject", StringUtils.trimToEmpty((String)TurbineUtils.GetPassedParameter("search_value",data)));
 		}
 
-        if(StringUtils.isNotBlank((String) org.nrg.xdat.turbine.utils.TurbineUtils.GetPassedParameter("pvisit", data))) {
-            XnatPvisitdata visit = XnatPvisitdata.getXnatPvisitdatasById((String) org.nrg.xdat.turbine.utils.TurbineUtils.GetPassedParameter("pvisit", data), TurbineUtils.getUser(data), false);
-            context.put("subject", visit.getSubjectId());
-            context.put("visit", visit.getId());
+        if(StringUtils.isNotBlank((String) TurbineUtils.GetPassedParameter("pvisit", data))) {
+            final XnatPvisitdata visit = XnatPvisitdata.getXnatPvisitdatasById(TurbineUtils.GetPassedParameter("pvisit", data), TurbineUtils.getUser(data), false);
+            if (visit != null) {
+                context.put("subject", visit.getSubjectId());
+                context.put("visit", visit.getId());
+            }
         }
 
         try {
@@ -62,8 +62,7 @@ public class LaunchUploadApplet extends UploadAppletScreen {
 	        if (json != null) {
 	            try {
 	            	//we have JSON, so, create applet parameters from it.
-	            	ObjectMapper mapper = new ObjectMapper();
-	            	AppletConfig jsonParams = mapper.readValue(json, AppletConfig.class);
+	            	AppletConfig jsonParams = getSerializer().deserializeJson(json, AppletConfig.class);
 
 	            	if(jsonParams.getLaunch() != null){
 	            		for(String key:jsonParams.getLaunch().keySet()){
@@ -79,8 +78,8 @@ public class LaunchUploadApplet extends UploadAppletScreen {
 		}
 	}
 
-    public interface ContextAction {
-        public void execute(RunData data, Context context);
+    private interface ContextAction {
+        void execute(RunData data, Context context);
     }
 
     private void dynamicContextExpansion(RunData data, Context context) throws Exception {
diff --git a/src/main/java/org/nrg/xnat/turbine/modules/screens/Login.java b/src/main/java/org/nrg/xnat/turbine/modules/screens/Login.java
new file mode 100644
index 0000000000000000000000000000000000000000..8b2785b2df6b22e7853a4bc3ad43b6a5adb02cc3
--- /dev/null
+++ b/src/main/java/org/nrg/xnat/turbine/modules/screens/Login.java
@@ -0,0 +1,44 @@
+/*
+ * org.nrg.xnat.turbine.modules.screens.ManageProtocol
+ * XNAT http://www.xnat.org
+ * Copyright (c) 2013, Washington University School of Medicine
+ * All Rights Reserved
+ *
+ * Released under the Simplified BSD.
+ *
+ * Author: Justin Cleveland <clevelandj@wustl.edu> (jcleve01)
+ * Last modified 1/22/2016 3:20 PM
+ */
+package org.nrg.xnat.turbine.modules.screens;
+
+import org.apache.log4j.Logger;
+import org.apache.turbine.util.RunData;
+import org.apache.velocity.context.Context;
+import org.nrg.xdat.XDAT;
+import org.nrg.xnat.services.ThemeService;
+
+public class Login extends org.nrg.xdat.turbine.modules.screens.Login {
+    public final static Logger logger = Logger.getLogger(XDATScreen_themes.class);
+    @Override
+    protected void doBuildTemplate(RunData data, Context context) throws Exception {
+        ThemeService themeService = XDAT.getContextService().getBean(ThemeService.class);
+//        String themedLoginPath = themeService.getThemePage("Login");
+//        if(themedLoginPath != null) {
+//            doRedirect(data, themedLoginPath);
+//            data.setRedirectURI(themedLoginPath);
+//        }
+        String themedRedirect = themeService.getThemePage("Login");
+        if(themedRedirect != null) {
+            context.put("themedRedirect", themedRedirect);
+            return;
+        }
+        String themedStyle = themeService.getThemePage("theme", "style");
+        if(themedStyle != null) {
+            context.put("themedStyle", themedStyle);
+        }
+        String themedScript = themeService.getThemePage("theme", "script");
+        if(themedScript != null) {
+            context.put("themedScript", themedScript);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/nrg/xnat/turbine/modules/screens/MRXMLSearch.java b/src/main/java/org/nrg/xnat/turbine/modules/screens/MRXMLSearch.java
index 45d5a4b2e9683368b82d5ff77c941fdcb54d4b6e..3314eb0c765e2adfab101c53f6b7a489b9e3ee5b 100644
--- a/src/main/java/org/nrg/xnat/turbine/modules/screens/MRXMLSearch.java
+++ b/src/main/java/org/nrg/xnat/turbine/modules/screens/MRXMLSearch.java
@@ -18,6 +18,7 @@ import javax.servlet.http.HttpServletResponse;
 import javax.xml.transform.TransformerConfigurationException;
 import javax.xml.transform.TransformerFactoryConfigurationError;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 import org.apache.turbine.util.RunData;
 import org.nrg.xdat.base.BaseElement;
@@ -35,7 +36,7 @@ import org.nrg.xft.schema.Wrappers.GenericWrapper.GenericWrapperElement;
 import org.nrg.xft.schema.Wrappers.GenericWrapper.GenericWrapperField;
 import org.nrg.xft.schema.Wrappers.XMLWrapper.SAXWriter;
 import org.nrg.xft.security.UserI;
-import org.nrg.xft.utils.StringUtils;
+import org.nrg.xft.utils.XftStringUtils;
 import org.xml.sax.SAXException;
 
 /**
@@ -131,7 +132,7 @@ created in buildPDF.
                             
                         }else{
                             if (adjustPath.equals("fullpath")){
-                                String root= StringUtils.ReplaceStr(mr.getArchiveRootPath(),"\\","/");
+                                String root= StringUtils.replace(mr.getArchiveRootPath(), "\\", "/");
                                 System.out.println(root);
                                 if (!root.endsWith("/")){
                                     root += "/";
diff --git a/src/main/java/org/nrg/xnat/turbine/modules/screens/UploadApplet.java b/src/main/java/org/nrg/xnat/turbine/modules/screens/UploadApplet.java
index b98bf84bcd5d372a7011f096a1c87b5bb851172a..c78e6e1952416dd77fbc30f17c6b9cecb113a17f 100644
--- a/src/main/java/org/nrg/xnat/turbine/modules/screens/UploadApplet.java
+++ b/src/main/java/org/nrg/xnat/turbine/modules/screens/UploadApplet.java
@@ -12,7 +12,6 @@ package org.nrg.xnat.turbine.modules.screens;
 
 import org.apache.turbine.util.RunData;
 import org.apache.velocity.context.Context;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.nrg.xdat.turbine.utils.TurbineUtils;
 import org.nrg.xnat.turbine.utils.ArcSpecManager;
 import org.nrg.xnat.utils.AppletConfig;
@@ -20,6 +19,7 @@ import org.nrg.xnat.utils.XnatHttpUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+@SuppressWarnings("unused")
 public class UploadApplet extends UploadAppletScreen {
 	private static final Logger logger = LoggerFactory.getLogger(UploadApplet.class);
 	
@@ -41,19 +41,19 @@ public class UploadApplet extends UploadAppletScreen {
         	// HH:MM or if HH:MM == 00:00 the applet will only verify the scan is on the same day as mm/dd/yyyy. If it receives no
         	// session_date, it will prompt the user for one.  With that said, we want to build up the mm/dd/yyyy string if we can.
         	// we'll do it here.
-        	String hhmm = " 00:00";
+        	String time = " 00:00";
         	if (TurbineUtils.HasPassedParameter("session_time_h", data) && TurbineUtils.HasPassedParameter("session_time_m", data)) {
         		// parameters are set with drop-downs so no need to validate here.
         		String hr = (String)TurbineUtils.GetPassedParameter("session_time_h", data);
         		String mm =(String)TurbineUtils.GetPassedParameter("session_time_m", data);
         		try{
         			//poor man's validation
-        			hhmm =  Integer.parseInt(hr) + ":" + Integer.parseInt(mm);
+        			time =  Integer.parseInt(hr) + ":" + Integer.parseInt(mm);
         		} catch (Exception e){
         			//if one or both aren't an integer, we'll get here, which is fine. it just means they didn't select a time. 
         		}
         	}
-            context.put("session_date", ((String)TurbineUtils.GetPassedParameter("session_date", data)).replace('.', '/') + " " + hhmm);
+            context.put("session_date", ((String)TurbineUtils.GetPassedParameter("session_date", data)).replace('.', '/') + " " + time);
         } else if (TurbineUtils.HasPassedParameter("no_session_date", data)) {
             context.put("session_date", "no_session_date");
         }
@@ -67,8 +67,7 @@ public class UploadApplet extends UploadAppletScreen {
 	        if (json != null) {
 	            try {
 	            	//we have JSON, so, create applet parameters from it.
-	            	ObjectMapper mapper = new ObjectMapper();
-	            	AppletConfig jsonParams = mapper.readValue(json, AppletConfig.class);
+	            	AppletConfig jsonParams = getSerializer().deserializeJson(json, AppletConfig.class);
 	            	StringBuilder sb = new StringBuilder();
 	            	if(jsonParams.getParameters() != null) {
 	            		for(String key:jsonParams.getParameters().keySet()) {
diff --git a/src/main/java/org/nrg/xnat/turbine/modules/screens/UploadAppletScreen.java b/src/main/java/org/nrg/xnat/turbine/modules/screens/UploadAppletScreen.java
index b6fdafdd84e09fbcfb55b1bf8919e46b867c46fd..46ad360a7cc91d8c106c383877badbb8ad1f263b 100644
--- a/src/main/java/org/nrg/xnat/turbine/modules/screens/UploadAppletScreen.java
+++ b/src/main/java/org/nrg/xnat/turbine/modules/screens/UploadAppletScreen.java
@@ -8,11 +8,17 @@ import org.nrg.xdat.security.helpers.Permissions;
 import org.nrg.xdat.turbine.modules.screens.SecureScreen;
 import org.nrg.xft.security.UserI;
 import org.nrg.xnat.utils.AppletConfig;
+import org.nrg.xnat.utils.SerializerService;
 
 /**
  * Contains basic methods used by upload applet screen classes.
  */
 public abstract class UploadAppletScreen extends SecureScreen {
+
+    protected UploadAppletScreen() {
+        _serializer = XDAT.getContextService().getBean(SerializerService.class);
+    }
+
     protected org.nrg.config.entities.Configuration getAppletConfiguration(final UserI user, final String projectName) {
         //grab the applet config. Project level if it exists, otherwise, do the site-wide
         ConfigService configService = XDAT.getConfigService();
@@ -53,4 +59,10 @@ public abstract class UploadAppletScreen extends SecureScreen {
         }
         return config;
     }
+
+    protected SerializerService getSerializer() {
+        return _serializer;
+    }
+
+    private final SerializerService _serializer;
 }
diff --git a/src/main/java/org/nrg/xnat/turbine/modules/screens/XDATScreen_admin_options.java b/src/main/java/org/nrg/xnat/turbine/modules/screens/XDATScreen_admin_options.java
new file mode 100644
index 0000000000000000000000000000000000000000..3bb1cb82c45c4456bcd12ebc23c1272afaa89fa2
--- /dev/null
+++ b/src/main/java/org/nrg/xnat/turbine/modules/screens/XDATScreen_admin_options.java
@@ -0,0 +1,40 @@
+/*
+ * org.nrg.xnat.turbine.modules.screens.ManageProtocol
+ * XNAT http://www.xnat.org
+ * Copyright (c) 2013, Washington University School of Medicine
+ * All Rights Reserved
+ *
+ * Released under the Simplified BSD.
+ *
+ * Author: Justin Cleveland <clevelandj@wustl.edu>
+ * Last modified 1/22/2016 3:20 PM
+ */
+
+package org.nrg.xnat.turbine.modules.screens;
+
+import org.apache.log4j.Logger;
+import org.apache.turbine.util.RunData;
+import org.apache.velocity.context.Context;
+import org.nrg.xdat.XDAT;
+import org.nrg.xdat.turbine.modules.screens.SecureScreen;
+import org.nrg.xnat.services.ThemeService;
+
+
+/**
+ * Created by jcleve01 on 1/22/2016.
+ */
+public class XDATScreen_admin_options extends SecureScreen  {
+    public final static Logger logger = Logger.getLogger(XDATScreen_admin_options.class);
+    @Override
+    protected void doBuildTemplate(RunData data, Context context) throws Exception {
+        ThemeService themeService = XDAT.getContextService().getBean(ThemeService.class);
+        String themedStyle = themeService.getThemePage("theme", "style");
+        if (themedStyle != null) {
+            context.put("themedStyle", themedStyle);
+        }
+        String themedScript = themeService.getThemePage("theme", "script");
+        if (themedScript != null) {
+            context.put("themedScript", themedScript);
+        }
+    }
+}
diff --git a/src/main/java/org/nrg/xnat/turbine/modules/screens/XDATScreen_download_sessions.java b/src/main/java/org/nrg/xnat/turbine/modules/screens/XDATScreen_download_sessions.java
index 08bdf5e76be92a33a9ddac4e64ac08dc075be90e..20a328510b147392563bb94c90d8cb77204e962f 100644
--- a/src/main/java/org/nrg/xnat/turbine/modules/screens/XDATScreen_download_sessions.java
+++ b/src/main/java/org/nrg/xnat/turbine/modules/screens/XDATScreen_download_sessions.java
@@ -15,6 +15,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.turbine.util.RunData;
 import org.apache.velocity.context.Context;
 import org.nrg.xdat.schema.SchemaElement;
@@ -24,7 +25,7 @@ import org.nrg.xdat.turbine.utils.TurbineUtils;
 import org.nrg.xft.XFTTable;
 import org.nrg.xft.exception.DBPoolException;
 import org.nrg.xft.security.UserI;
-import org.nrg.xft.utils.StringUtils;
+import org.nrg.xft.utils.XftStringUtils;
 
 import com.google.common.collect.Lists;
 
@@ -42,7 +43,7 @@ public class XDATScreen_download_sessions extends SecureScreen {
             String value = (String) TurbineUtils.GetPassedParameter("search_value", data);
             
             // TODO: For now, hard-limit this to MR sessions.
-            if(!StringUtils.IsEmpty(element) && !StringUtils.IsEmpty(field) && !StringUtils.IsEmpty(value)){
+            if(StringUtils.isNotBlank(element) && StringUtils.isNotBlank(field) && StringUtils.isNotBlank(value)){
             	SchemaElement se=SchemaElement.GetElement(element);
             	if(se.getGenericXFTElement().instanceOf("xnat:imageSessionData")){
             		sessionList = new ArrayList<String>();
@@ -69,7 +70,7 @@ public class XDATScreen_download_sessions extends SecureScreen {
                     }
                     if(s.contains("'"))
                     {
-                    	s= StringUtils.RemoveChar(s, '\'');
+                    	s= StringUtils.remove(s, '\'');
                     }
                     sessionString+="'" + s + "'";
             	}
diff --git a/src/main/java/org/nrg/xnat/turbine/modules/screens/XDATScreen_edit_xnat_qcManualAssessorData.java b/src/main/java/org/nrg/xnat/turbine/modules/screens/XDATScreen_edit_xnat_qcManualAssessorData.java
index 31ef94bdbe193409e3c3e9eb7b77cfd18921701d..508bcae35362c9d3fe887bb211562de9793b4f3d 100644
--- a/src/main/java/org/nrg/xnat/turbine/modules/screens/XDATScreen_edit_xnat_qcManualAssessorData.java
+++ b/src/main/java/org/nrg/xnat/turbine/modules/screens/XDATScreen_edit_xnat_qcManualAssessorData.java
@@ -10,6 +10,7 @@
  */
 package org.nrg.xnat.turbine.modules.screens;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.turbine.util.RunData;
 import org.nrg.xdat.model.XnatImagescandataI;
 import org.nrg.xdat.om.*;
@@ -18,7 +19,6 @@ import org.nrg.xft.ItemI;
 import org.nrg.xft.XFTItem;
 import org.nrg.xft.schema.Wrappers.GenericWrapper.GenericWrapperElement;
 import org.nrg.xft.security.UserI;
-import org.nrg.xft.utils.StringUtils;
 
 import java.util.Calendar;
 
@@ -31,11 +31,11 @@ public class XDATScreen_edit_xnat_qcManualAssessorData
 		final UserI user = TurbineUtils.getUser(data);
 		final XnatQcmanualassessordata qcAccessor = new XnatQcmanualassessordata(XFTItem.NewItem(getElementName(), user));
 		final String search_element = TurbineUtils.GetSearchElement(data);
-		if (!StringUtils.IsEmpty(search_element)) {
+		if (StringUtils.isNotBlank(search_element)) {
 			final GenericWrapperElement se = GenericWrapperElement.GetElement(search_element);
 			if (se.instanceOf(XnatImagesessiondata.SCHEMA_ELEMENT_NAME)) {
 				final String search_value = ((String)org.nrg.xdat.turbine.utils.TurbineUtils.GetPassedParameter("search_value",data));
-				if (!StringUtils.IsEmpty(search_value)) {
+				if (StringUtils.isNotBlank(search_value)) {
 					XnatImagesessiondata imageSession = new XnatImagesessiondata(TurbineUtils.GetItemBySearch(data));
 
 					// set defaults for new qc assessors
diff --git a/src/main/java/org/nrg/xnat/turbine/modules/screens/XDATScreen_themes.java b/src/main/java/org/nrg/xnat/turbine/modules/screens/XDATScreen_themes.java
index 54957d5e43cb9116eeb4a604a67e99f92696938b..85a637e82cefc55d0ad151c935d10300c1a0e5fc 100644
--- a/src/main/java/org/nrg/xnat/turbine/modules/screens/XDATScreen_themes.java
+++ b/src/main/java/org/nrg/xnat/turbine/modules/screens/XDATScreen_themes.java
@@ -15,8 +15,9 @@ package org.nrg.xnat.turbine.modules.screens;
 import org.apache.log4j.Logger;
 import org.apache.turbine.util.RunData;
 import org.apache.velocity.context.Context;
+import org.nrg.xdat.XDAT;
 import org.nrg.xdat.turbine.modules.screens.SecureScreen;
-import org.nrg.xnat.restlet.extensions.ThemeRestlet;
+import org.nrg.xnat.services.ThemeService;
 
 
 /**
@@ -26,5 +27,14 @@ public class XDATScreen_themes extends SecureScreen  {
     public final static Logger logger = Logger.getLogger(XDATScreen_themes.class);
     @Override
     protected void doBuildTemplate(RunData data, Context context) throws Exception {
+        ThemeService themeService = XDAT.getContextService().getBean(ThemeService.class);
+        String themedStyle = themeService.getThemePage("theme", "style");
+        if (themedStyle != null) {
+            context.put("themedStyle", themedStyle);
+        }
+        String themedScript = themeService.getThemePage("theme", "script");
+        if (themedScript != null) {
+            context.put("themedScript", themedScript);
+        }
     }
 }
diff --git a/src/main/java/org/nrg/xnat/turbine/utils/IDGenerator.java b/src/main/java/org/nrg/xnat/turbine/utils/IDGenerator.java
index 2dc7ce1ba84753b3524259743ad5f9eab4e177b8..9b460146f25db265835cffeb2249ebf6dbef8eaa 100644
--- a/src/main/java/org/nrg/xnat/turbine/utils/IDGenerator.java
+++ b/src/main/java/org/nrg/xnat/turbine/utils/IDGenerator.java
@@ -10,10 +10,10 @@
  */
 package org.nrg.xnat.turbine.utils;
 
+import org.apache.commons.lang3.StringUtils;
 import org.nrg.xft.XFT;
 import org.nrg.xft.XFTTable;
 import org.nrg.xft.identifier.IDGeneratorI;
-import org.nrg.xft.utils.StringUtils;
 import org.nrg.xnat.services.impl.hibernate.HibernateHostInfoService;
 
 import java.util.ArrayList;
@@ -172,11 +172,11 @@ public class IDGenerator implements IDGeneratorI {
 	 */
 	private String getSiteID(){
 		String site_id = XFT.GetSiteID();
-		site_id = StringUtils.ReplaceStr(site_id, " ", "");
-		site_id = StringUtils.ReplaceStr(site_id, "-", "_");
-		site_id = StringUtils.ReplaceStr(site_id, "\"", "");
-		site_id = StringUtils.ReplaceStr(site_id, "'", "");
-		site_id = StringUtils.ReplaceStr(site_id, "^", "");
+		site_id = StringUtils.replace(site_id, " ", "");
+		site_id = StringUtils.replace(site_id, "-", "_");
+		site_id = StringUtils.replace(site_id, "\"", "");
+		site_id = StringUtils.replace(site_id, "'", "");
+		site_id = StringUtils.replace(site_id, "^", "");
 		return site_id;
 	}
 	
diff --git a/src/main/java/org/nrg/xnat/turbine/utils/ProjectAccessRequest.java b/src/main/java/org/nrg/xnat/turbine/utils/ProjectAccessRequest.java
index 5ad409e39e5ead1c435e65a2aafc1b4ae151cdc2..bf86d90aa41ad9247430546944cf984d4b23ee9d 100644
--- a/src/main/java/org/nrg/xnat/turbine/utils/ProjectAccessRequest.java
+++ b/src/main/java/org/nrg/xnat/turbine/utils/ProjectAccessRequest.java
@@ -10,26 +10,8 @@
  */
 package org.nrg.xnat.turbine.utils;
 
-import static org.nrg.xdat.om.base.BaseXnatProjectdata.getProjectByIDorAlias;
-import static org.nrg.xdat.om.base.auto.AutoXnatProjectdata.SCHEMA_ELEMENT_NAME;
-import static org.nrg.xdat.om.base.auto.AutoXnatProjectdata.getXnatProjectdatasById;
-import static org.nrg.xdat.om.base.auto.AutoXnatProjectdata.logger;
-
-import java.io.StringWriter;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import javax.mail.MessagingException;
-
 import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 import org.apache.velocity.Template;
 import org.apache.velocity.app.Velocity;
@@ -48,11 +30,20 @@ import org.nrg.xft.event.EventUtils;
 import org.nrg.xft.event.persist.PersistentWorkflowI;
 import org.nrg.xft.exception.DBPoolException;
 import org.nrg.xft.security.UserI;
-import org.nrg.xft.utils.StringUtils;
 import org.nrg.xnat.exceptions.XFTItemNotFoundException;
 import org.nrg.xnat.exceptions.XNATException;
 import org.nrg.xnat.utils.WorkflowUtils;
 
+import javax.mail.MessagingException;
+import java.io.StringWriter;
+import java.sql.SQLException;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static org.nrg.xdat.om.base.BaseXnatProjectdata.getProjectByIDorAlias;
+import static org.nrg.xdat.om.base.auto.AutoXnatProjectdata.*;
+
 public class ProjectAccessRequest {
     public static boolean CREATED_PAR_TABLE = false;
 
@@ -391,10 +382,8 @@ public class ProjectAccessRequest {
                 CreatePARTable();
             }
 
-            String query = String.format("INSERT INTO xs_par_table (proj_id,user_id,level) VALUES ('%s', %d, '%s');", pID, user.getID(), StringUtils.RemoveChar(level, '\''));
+            String query = String.format("INSERT INTO xs_par_table (proj_id,user_id,level) VALUES ('%s', %d, '%s');", pID, user.getID(), StringUtils.remove(level, '\''));
             PoolDBUtils.ExecuteNonSelectQuery(query, user.getDBName(), user.getLogin());
-        } catch (SQLException e) {
-            _logger.error("", e);
         } catch (Exception e) {
             _logger.error("", e);
         }
@@ -474,7 +463,7 @@ public class ProjectAccessRequest {
 	             CreatePARTable();
 	         }
 
-	         invitee = StringUtils.RemoveChar(invitee, '\'');
+	         invitee = StringUtils.remove(invitee, '\'');
             String guid = UUID.randomUUID().toString();
 
             StringBuilder query = new StringBuilder("INSERT INTO xs_par_table (email, guid, proj_id, approver_id, level) VALUES ('");
diff --git a/src/main/java/org/nrg/xnat/utils/SerializerService.java b/src/main/java/org/nrg/xnat/utils/SerializerService.java
new file mode 100644
index 0000000000000000000000000000000000000000..529f10b6777439e32150894975a3fb9f594621f2
--- /dev/null
+++ b/src/main/java/org/nrg/xnat/utils/SerializerService.java
@@ -0,0 +1,53 @@
+package org.nrg.xnat.utils;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.stereotype.Service;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import java.io.IOException;
+
+@Service
+public class SerializerService {
+    public JsonNode deserializeJson(final String json) throws IOException {
+        return _jsonObjectMapper.readTree(json);
+    }
+
+    public <T> T deserializeJson(final String json, final Class<T> clazz) throws IOException {
+        return _jsonObjectMapper.readValue(json, clazz);
+    }
+
+    public <T> T deserializeJson(final String json, final TypeReference<T> typeRef) throws IOException {
+        return _jsonObjectMapper.readValue(json, typeRef);
+    }
+
+    public <T> String toJson(final T instance) throws IOException {
+        return _jsonObjectMapper.writeValueAsString(instance);
+    }
+
+    public JsonNode deserializeYaml(final String yaml) throws IOException {
+        return _yamlObjectMapper.readTree(yaml);
+    }
+
+    public <T> T deserializeYaml(final String yaml, Class<T> clazz) throws IOException {
+        return _yamlObjectMapper.readValue(yaml, clazz);
+    }
+
+    public <T> T deserializeYaml(final String yaml, final TypeReference<T> typeRef) throws IOException {
+        return _yamlObjectMapper.readValue(yaml, typeRef);
+    }
+
+    public <T> String toYaml(final T instance) throws IOException {
+        return _yamlObjectMapper.writeValueAsString(instance);
+    }
+
+    @Inject
+    @Named("jsonObjectMapper")
+    private ObjectMapper _jsonObjectMapper;
+
+    @Inject
+    @Named("yamlObjectMapper")
+    private ObjectMapper _yamlObjectMapper;
+}
diff --git a/src/main/resources/META-INF/xnat/spawner/site-admin-elements.yaml b/src/main/resources/META-INF/xnat/spawner/site-admin-elements.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..f6e1311c8fde5ce996662230e772a31353b1c24e
--- /dev/null
+++ b/src/main/resources/META-INF/xnat/spawner/site-admin-elements.yaml
@@ -0,0 +1,117 @@
+# Initial element declarations for the site admin user interface elements.
+siteId:
+    label: Site ID
+    type: text
+    id: site-id
+    description: Identifies your XNAT site.
+    placeholder: Enter your XNAT site ID...
+    url: /xapi/services/prefs/siteId/{siteId}
+    default: XNAT
+    validation:
+        required: true
+        type: xnat-id
+
+siteUrl:
+    label: Site Url
+    type: url
+    description: The root URL for the site. This is passed to external services.
+    placeholder: Enter the URL for your XNAT site...
+    value: https://cnda.wustl.edu
+    validation:
+        required: true
+
+siteDescriptionPage:
+    id: desc-page
+    label: Page
+    type: site.path
+    description: The page to display for the site description, e.g. /screens/site_description.vm.
+    value: /screens/site_description.vm
+    placeholder: Enter a page for your site description...
+    validation:
+        required: true
+
+siteDescriptionMarkdown:
+    id: desc-markdown
+    label: Text (Markdown)
+    type: markdown
+    tooltip: XNAT allows you to use GitHub-flavored Markdown to create and format your own site description. [&gt;&gt; View Tutorial](http://foobar)
+    description: Compose a site description without referencing a template or site page.
+    validation:
+        required: true
+
+siteDescription:
+    label: Site Description
+    type: composite
+    selection: radio
+    url: /data/services/prefs/{desc}
+    children:
+        ${siteDescriptionPage}
+        ${siteDescriptionMarkdown}
+
+landingPage:
+    label: Landing Page
+    type: site.path
+    description: The page to display when the user logs in.
+    value: /screens/QuickSearch.vm
+    placeholder: Enter the default landing page...
+    overrides:
+        target: homePage
+        type: checkbox
+        position: right
+        description: Use this as my home page.
+        hideTarget: false
+    validation:
+        required: true
+
+homePage:
+    label: Home Page
+    type: site.path
+    description: The page to display when the user clicks the home link.
+    value: /screens/AdminUsers.vm
+    placeholder: Enter the default home page...
+    validation:
+        required: true
+
+siteInfo:
+    label: Site Information
+    type: panel
+    controls:
+       ${siteId}
+       ${siteUrl}
+       ${siteDescription}
+       ${landingPage}
+       ${homePage}
+
+siteAdminEmail:
+    label: Site Admin Email
+    type: email
+    placeholder: Administrator email address...
+    url: /xapi/services/prefs/siteAdminEmail/{siteAdminEmail}
+    validation:
+        required: true
+
+adminInfo:
+    label: Admin Information
+    type: panel
+    controls:
+        ${siteAdminEmail}
+
+siteSetup:
+    label: Site Setup
+    type: tab
+    contents:
+        ${siteInfo}
+        ${adminInfo}
+
+xnatSetup:
+    label: XNAT Setup
+    type: tabGroup
+    tabs:
+        ${siteSetup}
+
+siteAdmin:
+    label: Administer XNAT
+    type: page
+    contains: tabs
+    contents:
+        ${xnatSetup}
diff --git a/src/main/webapp/META-INF/context.xml b/src/main/webapp/META-INF/context.xml
index 16bd2bec3ce56149a803754e2972b96d298abe37..b4c53f63c28668cc23be5f6c85c64b2487290da0 100644
--- a/src/main/webapp/META-INF/context.xml
+++ b/src/main/webapp/META-INF/context.xml
@@ -1,5 +1,5 @@
-<?xml version='1.0' encoding='utf-8'?>
-<Context antiResourceLocking="true">
+<?xml version="1.0" encoding="utf-8"?>
+<Context>
     <Loader className="org.apache.catalina.loader.VirtualWebappLoader" searchVirtualFirst="true" virtualClasspath="${xnat.home}/plugins/*.jar"/>
     <JarScanner scanAllDirectories="true" />
     <Parameter name="xnatHome" value="${xnat.home}"/>
diff --git a/src/main/webapp/WEB-INF/conf/mq-context.xml b/src/main/webapp/WEB-INF/conf/mq-context.xml
index a2d518e2bbc4e673c2e14f38ff9f787498efa560..5e6824874667f5f376855eae0d6c41820fdf3fd9 100644
--- a/src/main/webapp/WEB-INF/conf/mq-context.xml
+++ b/src/main/webapp/WEB-INF/conf/mq-context.xml
@@ -24,7 +24,7 @@
     <context:property-placeholder location="WEB-INF/conf/services.properties, WEB-INF/conf/services-custom.properties" ignore-resource-not-found="true" ignore-unresolvable="true" />
 
     <!-- embedded ActiveMQ Broker -->
-    <amq:broker id="activeMQBroker" brokerName="activeMQBroker" useJmx="false" persistent="true" schedulerSupport="false" >
+    <amq:broker id="activeMQBroker" brokerName="activeMQBroker" useJmx="false" persistent="true" schedulerSupport="false" useShutdownHook="false">
         <amq:persistenceAdapter>
             <!-- We'll use XNAT's data source, make sure the Spring ID matches. (For unit testing it's provided by the Spring test
                 context) Also, turning off locking, as we've no need for it with a single broker, and it was causing errors in the ActiveMQ
diff --git a/src/main/webapp/WEB-INF/conf/services.properties b/src/main/webapp/WEB-INF/conf/services.properties
index de1d5c7fac4e77dbee428cf9994d40c80eea32e3..429b50d9c5cb5010a892341c71e723face3bb53e 100644
--- a/src/main/webapp/WEB-INF/conf/services.properties
+++ b/src/main/webapp/WEB-INF/conf/services.properties
@@ -27,7 +27,7 @@ mailserver.port=25
 mailserver.username=
 mailserver.password=
 mailserver.protocol=smtp
-mailserver.admin=administrator@xnat.org
+mailserver.admin=jrherrick@wustl.edu
 mailserver.prefix=XNAT
 
 # Session XML rebuilder settings. interval is in minutes, repeat is in milliseconds.
diff --git a/src/main/webapp/WEB-INF/conf/xnat-security.xml b/src/main/webapp/WEB-INF/conf/xnat-security.xml
index a3bc84b97f89bb29b5dbf2480a5c3fcdd765f6ad..408afbb2e10a6b399c48d40656f8af9067904eac 100644
--- a/src/main/webapp/WEB-INF/conf/xnat-security.xml
+++ b/src/main/webapp/WEB-INF/conf/xnat-security.xml
@@ -122,6 +122,7 @@
                 <value>/images/**</value>
                 <value>/scripts/**</value>
                 <value>/style/**</value>
+                <value>/themes/**</value>
                 <value>/applet/**</value>
             </list>
         </property>
diff --git a/src/main/webapp/WEB-INF/tags/spawner/layout.tag b/src/main/webapp/WEB-INF/tags/spawner/layout.tag
new file mode 100644
index 0000000000000000000000000000000000000000..b096d29672bf34e178c3f841b9d897b312a33cfb
--- /dev/null
+++ b/src/main/webapp/WEB-INF/tags/spawner/layout.tag
@@ -0,0 +1,764 @@
+<%@tag description="Default layout template" pageEncoding="UTF-8" %>
+<%@attribute name="title" %>
+
+<!DOCTYPE html>
+<!--[if lt IE 7]> <html class="ie ie6 ltie7 ltie8 ltie9 ltie10 no-js"> <![endif]-->
+<!--[if IE 7]> <html class="ie ie7 ltie8 ltie9 ltie10 no-js"> <![endif]-->
+<!--[if IE 8]> <html class="ie ie8 ltie9 ltie10 no-js"> <![endif]-->
+<!--[if IE 9]> <html class="ie ie9 ltie10 no-js"> <![endif]-->
+<!--[if gt IE 9]><!-->
+<html class="no-js"> <!--<![endif]-->
+
+<head>
+    <title>${title}</title>
+
+    <!-- HeaderIncludes -->
+
+    <!-- path: /xnat-templates/navigations/HeaderIncludes -->
+
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta http-equiv="Pragma" content="no-cache">
+    <meta http-equiv="cache-control" content="max-age=0">
+    <meta http-equiv="cache-control" content="no-cache">
+    <meta http-equiv="expires" content="-1">
+    <meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT">
+
+
+    <!-- load polyfills before ANY other JavaScript -->
+    <script type="text/javascript" src="/scripts/polyfills.js"></script>
+
+    <!-- set global vars that are used often -->
+    <script type="text/javascript">
+
+        var XNAT = {};
+        var serverRoot = "";
+        var csrfToken = "66ccfc57-b8d2-4e90-8b9e-f1ff0b43e06c";
+        var showReason = typeof false != 'undefined' ? false : null;
+        var requireReason = typeof false != 'undefined' ? false : null;
+
+    </script>
+
+    <!-- XNAT global functions (no dependencies) -->
+    <script type="text/javascript" src="/scripts/globals.js"></script>
+
+    <!-- required libraries -->
+    <script>
+        // loads minified versions by default
+        // add ?debug=true to the query string
+        // or #debug to the url hash to load
+        // the non-minified versions
+        writeScripts([
+            scriptUrl('lib/loadjs/loadjs'),
+            scriptUrl('lib/jquery/jquery|.min'),
+            scriptUrl('lib/jquery/jquery-migrate-1.2.1|.min'),
+        ]);
+    </script>
+    <script type="text/javascript">
+        // use 'jq' to avoid _possible_ conflicts with Velocity
+        var jq = jQuery;
+    </script>
+
+    <!-- jQuery plugins -->
+    <link rel="stylesheet" type="text/css" href="/scripts/lib/jquery-plugins/chosen/chosen.min.css?v=1.7.0a1">
+    <script type="text/javascript" src="/scripts/lib/jquery-plugins/chosen/chosen.jquery.min.js"></script>
+    <script type="text/javascript" src="/scripts/lib/jquery-plugins/jquery.maskedinput.min.js"></script>
+    <script type="text/javascript" src="/scripts/lib/jquery-plugins/jquery.spawn.js"></script>
+
+    <!-- other libraries -->
+    <script type="text/javascript" src="/scripts/lib/spawn/spawn.js"></script>
+
+    <!-- XNAT utility functions -->
+    <script type="text/javascript" src="/scripts/utils.js"></script>
+
+    <script type="text/javascript">
+
+        /*
+         * XNAT global namespace object, which will not be overwriten if
+         * already defined. Also define some other top level namespaces.
+         */
+        extend(XNAT, {
+            /*
+             * Parent namespace that templates can use to put their
+             * own namespace
+             */
+            app: {
+                displayNames: {
+                    singular: {
+                        project: "Project",
+                        subject: "Subject",
+                        imageSession: "Session",
+                        mrSession: "MR Session"
+                    },
+                    plural: {
+                        project: "Projects",
+                        subject: "Subjects",
+                        imageSession: "Sessions",
+                        mrSession: "MR Sessions"
+                    }
+                },
+                siteId: "XNAT"
+            },
+            images: {
+                grnChk: "/images/checkmarkGreen.gif",
+                redChk: "/images/checkmarkRed.gif"
+            },
+            data: {
+                context: {
+                    projectName: "",
+                    projectID: "",
+                    project: "",
+                    xsiType: "",
+                    subjectLabel: "",
+                    subjectID: "",
+                    label: "",
+                    ID: ""
+                },
+                timestamp: jq.now() // timestamp for the page when it loads
+            }
+        });
+
+        if (XNAT.data.context.projectName === "") {
+            XNAT.data.context.projectName = "";
+        }
+
+        // 'page' object is same as 'context' - easier to remember?
+        XNAT.data.page = XNAT.data.context;
+
+        XNAT.app.showLeftBar = true;
+        XNAT.app.showLeftBarProjects = true;
+        XNAT.app.showLeftBarFavorites = true;
+        XNAT.app.showLeftBarSearch = true;
+        XNAT.app.showLeftBarBrowse = true;
+
+        window.available_elements = [];
+
+
+        window.available_elements.getByName = function (name) {
+            for (var aeC = 0; aeC < this.length; aeC++) {
+                if (this[aeC].element_name == name) {
+                    return this[aeC];
+                }
+            }
+            // return empty object if not found
+            return {}
+        };
+
+
+        // quickly reference today's date
+        XNAT.data.todaysDate = {};
+        // if today was January 23, 2013...
+        // m (1), mm (01), d (23), dd (23), yyyy (2013), ISO/iso (2013-01-23), US/us (01/23/2013)
+        (function (dateObj) {
+            dateObj.date = new Date();
+            dateObj.gotMonth = dateObj.date.getMonth();
+            dateObj.m = (dateObj.gotMonth + 1).toString();
+            dateObj.mm = (dateObj.m.length === 1) ? '0' + dateObj.m : dateObj.m;
+            dateObj.d = dateObj.date.getDate().toString();
+            dateObj.dd = (dateObj.d.length === 1) ? '0' + dateObj.d : dateObj.d;
+            dateObj.yyyy = dateObj.date.getFullYear().toString();
+            dateObj.ISO = dateObj.iso = dateObj.yyyy + '-' + dateObj.mm + '-' + dateObj.dd;
+            dateObj.US = dateObj.us = dateObj.mm + '/' + dateObj.dd + '/' + dateObj.yyyy;
+        })(XNAT.data.todaysDate);
+
+    </script>
+    <script type="text/javascript">
+        // initialize "Chosen" menus on DOM load
+        // all <select class="chosen-menu"> elements
+        // will be converted
+        // putting this here to be at the top of
+        // the jQuery DOM-ready queue
+        jq(function () {
+            chosenInit()
+        });
+    </script>
+    <script type="text/javascript" src="/scripts/xdat.js"></script>
+    <script type="text/javascript" src="/scripts/DynamicJSLoad.js"></script>
+
+    <!-- YAHOO USER INTERFACE files below here -->
+    <script type="text/javascript" src="/scripts/yui/build/yahoo-dom-event/yahoo-dom-event.js"></script>
+    <script type="text/javascript" src="/scripts/yui/build/event/event-min.js"></script>
+    <script type="text/javascript" src="/scripts/yui/build/container/container-min.js"></script>
+    <script type="text/javascript" src="/scripts/yui/build/menu/menu-min.js"></script>
+    <script type="text/javascript" src="/scripts/yui/build/element/element-beta-min.js"></script>
+    <script type="text/javascript" src="/scripts/yui/build/button/button-min.js"></script>
+    <script type="text/javascript" src="/scripts/yui/build/connection/connection-min.js"></script>
+    <script type="text/javascript" src="/scripts/yui/build/treeview/treeview-min.js"></script>
+    <script type="text/javascript" src="/scripts/yui/build/cookie/cookie-min.js"></script>
+    <script type="text/javascript" src="/scripts/yui/build/tabview/tabview-min.js"></script>
+    <script type="text/javascript" src="/scripts/yui/build/datasource/datasource-min.js"></script>
+    <script type="text/javascript" src="/scripts/yui/build/resize/resize-min.js"></script>
+    <script type="text/javascript" src="/scripts/yui/build/dragdrop/dragdrop-min.js"></script>
+    <script type="text/javascript" src="/scripts/yui/build/datatable/datatable-min.js"></script>
+    <script type="text/javascript" src="/scripts/yui/build/paginator/paginator-min.js"></script>
+    <script type="text/javascript" src="/scripts/yui/build/json/json-min.js"></script>
+    <script type="text/javascript" src="/scripts/yui/xnat_loader.js"></script>
+    <script type="text/javascript" src="/scripts/LeftBarTreeView.js"></script>
+    <script type="text/javascript" src="/scripts/justification/justification.js"></script>
+    <script type="text/javascript">
+
+        // The YUIDOM alias is used throughout XNAT
+        var YUIDOM = YAHOO.util.Dom;
+
+        XNAT.dom = getObject(XNAT.dom || {});
+        XNAT.dom.addFormCSRF = function (__form) {
+            __form = isDefined(__form) ? $$(__form) : jq('form');
+            __form.append('<input type="hidden" name="XNAT_CSRF" value="' + csrfToken + '">')
+        };
+
+        jq(function () {
+            // add hidden input with CSRF data
+            // to all forms on page load
+            XNAT.dom.addFormCSRF();
+        });
+
+    </script>
+
+    <!-- YUI css -->
+    <link rel="stylesheet" type="text/css" href="/scripts/yui/build/assets/skins/sam/skin.css?v=1.7.0a1">
+
+    <!-- xdat.css and xnat.css loaded last to override YUI styles -->
+    <link rel="stylesheet" type="text/css" href="/style/app.css?v=1.7.0a1">
+
+    <link rel="stylesheet" type="text/css" href="/scripts/xmodal-v1/xmodal.css?v=1.7.0a1">
+    <script type="text/javascript" src="/scripts/xmodal-v1/xmodal.js"></script>
+    <script type="text/javascript" src="/scripts/xmodal-v1/xmodal-migrate.js"></script>
+
+    <link rel="stylesheet" type="text/css" href="/scripts/tabWrangler/tabWrangler.css?v=1.7.0a1">
+    <script type="text/javascript" src="/scripts/tabWrangler/tabWrangler.js"></script>
+
+    <!-- date input stuff -->
+    <link type="text/css" rel="stylesheet" href="/scripts/yui/build/calendar/assets/skins/sam/calendar.css?v=1.7.0a1">
+    <script type="text/javascript" src="/scripts/yui/build/calendar/calendar-min.js"></script>
+    <script type="text/javascript" src="/scripts/ezCalendar.js"></script>
+
+    <!-- XNAT JLAPI scripts -->
+    <script type="text/javascript" src="/scripts/xnat/url.js"></script>
+    <script type="text/javascript" src="/scripts/xnat/xhr.js"></script>
+    <script type="text/javascript" src="/scripts/xnat/ui/popup.js"></script>
+    <script type="text/javascript" src="/scripts/xnat/ui/dialog.js"></script>
+
+    <!-- /HeaderIncludes -->
+
+    <!-- path: xnat-templates/navigations/bodyOpen -->
+</head>
+<body id="page_body" class="yui-skin-sam">
+<div id="page_wrapper">
+
+    <!-- path: xdat-templates/layouts/Default -->
+
+    <!-- START: xnat-templates/navigations/DefaultTop.vm -->
+    <script type="text/javascript" src="/scripts/lib/js.cookie.js"></script>
+
+    <style type="text/css">
+        #attention_icon {
+            float: left;
+            padding-top: 7px;
+            padding-left: 11px;
+        }
+
+        #attention_icon:hover {
+            cursor: pointer;
+        }
+    </style>
+
+    <div id="user_bar">
+        <div class="inner">
+            <img id="attention_icon" src="/images/attention.png" style="display:none;"
+                 alt="attention needed - click for more info" title="attention needed - click for more info">
+                                    <span id="user_info">Logged in as: &nbsp;<a
+                                            href="/app/template/XDATScreen_UpdateUser.vm">admin</a> <b>|</b><span
+                                            class="tip_icon" style="margin-right:3px;left:2px;top:3px;">
+            <span class="tip shadowed"
+                  style="top:20px;z-index:10000;white-space:normal;left:-150px;width:300px;background-color:#ffc;">Your XNAT session will auto-logout after a certain period of inactivity. You can reset that timer without reloading the page by clicking "renew."</span>
+        </span>
+                        Auto-logout in: <b id="timeLeft">-:--:--</b> - <a id="timeLeftRenew" href="javascript:"
+                                                                          onClick="XNAT.app.timeout.handleOk()">renew</a> <b>|</b> <a
+                                                id="logout_user" href="/app/action/LogoutUser">Logout</a></span>
+            <script type="text/javascript">
+                Cookies.set('guest', 'false', {path: '/'});
+            </script>
+            <div class="clear"></div>
+        </div>
+    </div><!-- /user_bar -->
+
+
+    <div id="main_nav">
+
+        <ul class="nav">
+            <!-- Sequence: 10 -->
+            <!-- allowGuest: true -->
+            <li>
+                <a id="nav-home" title="Home" href="/app/template/Index.vm">&nbsp;</a>
+                <script>
+                    $('#nav-home').css({
+                        width: '30px',
+                        backgroundImage: "url('/images/xnat-nav-logo-white-lg.png')",
+                        backgroundRepeat: 'no-repeat',
+                        backgroundSize: '32px',
+                        backgroundPosition: 'center'
+                    });
+                </script>
+            </li>
+            <!-- Sequence: 20 -->
+
+            <li><a href="#new">New</a>
+                <ul>
+                    <!-- Sequence: 10 -->
+                    <li><a href="/app/template/XDATScreen_add_xnat_projectData.vm">Project</a></li>
+                    <!-- -->
+                    <!-- -->
+                    <li>
+                        <a href="/app/action/XDATActionRouter/xdataction/edit/search_element/xnat%3AsubjectData">Subject</a>
+                    </li>
+                    <!-- -->
+
+                    <!-- no object found -->
+                    <!-- no object found -->
+                    <!-- no object found -->
+                    <!-- -->
+                    <li><a href="/app/template/XDATScreen_add_experiment.vm">Experiment</a></li>
+                    <!-- -->
+                    <!-- -->
+                </ul>
+            </li>
+            <!-- Sequence: 30 -->
+            <li><a href="#upload">Upload</a>
+                <ul>
+                    <!-- Sequence: 10 -->
+                    <!-- Upload/Default -->
+                    <li><a href="/app/template/LaunchUploadApplet.vm">Images</a></li>
+                    <li><a href="/app/template/XMLUpload.vm">XML</a></li>
+                    <li><a href="/app/template/XDATScreen_uploadCSV.vm">Spreadsheet</a></li>
+                    <li><a href="/app/template/XDATScreen_prearchives.vm">Go to prearchive</a></li>
+                </ul>
+            </li>
+            <!-- Sequence: 40 -->
+
+            <li><a href="#adminbox">Administer</a>
+                <ul>
+                    <!-- Sequence: 10 -->
+                    <li><a href="/app/template/XDATScreen_admin.vm">Users</a></li>
+                    <li><a href="/app/template/XDATScreen_groups.vm">Groups</a></li>
+                    <li><a href="/app/template/XDATScreen_dataTypes.vm">Data Types</a></li>
+                    <li><a href="/app/template/XDATScreen_email.vm">Email</a></li>
+                    <li><a href="/app/template/XDATScreen_manage_pipeline.vm">Pipelines</a></li>
+                    <li><a href="/app/template/Configuration.vm">Configuration</a></li>
+                    <li><a href="/app/template/Scripts.vm">Automation</a></li>
+                    <li><a href="/app/template/XDATScreen_admin_options.vm">More...</a></li>
+                </ul>
+            </li>
+
+            <!-- Title: Tools -->
+            <!-- Sequence: 50 -->
+            <!-- allowGuest: true -->
+
+            <li><a href="#tools">Tools</a>
+                <ul>
+                    <!-- Sequence: 10 -->
+                    <!-- allowGuest: true -->
+                    <li><a href="https://wiki.xnat.org/display/XNAT16/XNAT+Desktop" target="_blank">XNAT Desktop
+                        (XND)</a></li>
+                    <li><a href="http://nrg.wustl.edu/projects/DICOM/DicomBrowser.jsp" target="_blank">DICOM Browser</a>
+                    </li>
+                    <li><a href="https://wiki.xnat.org/display/XNAT16/XNAT+Client+Tools" target="_blank">Command Prompt
+                        Tools</a></li>
+                </ul>
+            </li>
+            <!-- Sequence: 60 -->
+            <li><a href="#help">Help</a>
+                <ul>
+                    <!-- Sequence: 10 -->
+                    <!-- Home/Default -->
+                    <li><a href="/app/template/ReportIssue.vm">Report a Problem</a></li>
+                    <li><a href="http://wiki.xnat.org/display/XNAT16/Home" target="_blank">Documentation</a></li>
+                </ul>
+            </li>
+        </ul>
+
+        <!-- search script -->
+        <script type="text/javascript">
+            <!--
+            function DefaultEnterKey(e, button) {
+                var keynum, keychar, numcheck;
+
+                if (window.event) // IE
+                {
+                    keynum = e.keyCode;
+                    if (keynum == 13) {
+                        submitQuickSearch();
+                        return true;
+                    }
+                }
+                else if (e) // Netscape/Firefox/Opera
+                {
+                    keynum = e.which;
+                    if (keynum == 13) {
+                        submitQuickSearch();
+                        return false;
+                    }
+                }
+                return true;
+            }
+
+            function submitQuickSearch() {
+                concealContent();
+                if (document.getElementById('quickSearchForm').value != "")
+                    document.getElementById('quickSearchForm').submit();
+            }
+
+            //-->
+        </script>
+        <!-- end search script -->
+
+        <style type="text/css">
+            #quickSearchForm .chosen-results {
+                max-height: 500px;
+            }
+
+            #quickSearchForm .chosen-results li {
+                padding-right: 20px;
+                white-space: nowrap;
+            }
+
+            #quickSearchForm .chosen-container .chosen-drop {
+                width: auto;
+                min-width: 180px;
+                max-width: 360px;
+            }
+
+            #quickSearchForm .chosen-container .chosen-drop .divider {
+                padding: 0;
+                overflow: hidden;
+            }
+        </style>
+
+        <form id="quickSearchForm" method="post" action="/app/action/QuickSearchAction">
+            <select id="stored-searches" data-placeholder="Stored Searches">
+                <option></option>
+                <optgroup>
+                    <option value="/app/template/XDATScreen_search_wizard1.vm">Advanced Search&hellip;</option>
+                </optgroup>
+                <optgroup class="stored-search-list">
+                    <option disabled>(no stored searches)</option>
+                    <!-- stored searches will show up here -->
+                </optgroup>
+            </select>
+            <input id="searchValue" class="clean" name="searchValue" type="text" maxlength="40" size="20" value=""/>
+            <button type="button" id="search_btn" class="btn2" onclick="submitQuickSearch();">Go</button>
+
+            <script>
+                $('#searchValue').each(function () {
+                    this.value = this.value || 'search';
+                    $(this).focus(function () {
+                        $(this).removeClass('clean');
+                        if (!this.value || this.value === 'search') {
+                            this.value = '';
+                        }
+                    })
+                });
+                $('#stored-searches').on('change', function () {
+                    if (this.value) {
+                        window.location.href = this.value;
+                    }
+                }).chosen({
+                    width: '150px',
+                    disable_search_threshold: 9,
+                    inherit_select_classes: true,
+                    placeholder_text_single: 'Stored Searches',
+                    search_contains: true
+                });
+            </script>
+        </form>
+
+
+    </div>
+    <!-- /main_nav -->
+
+    <!-- main_nav interactions -->
+    <script type="text/javascript">
+
+        (function () {
+
+            // cache it
+            var main_nav$ = jq('#main_nav > ul');
+
+            var body$ = jq('body');
+
+            var cover_up_count = 1;
+
+            function coverApplet(el$) {
+                var cover_up_id = 'cover_up' + cover_up_count++;
+                var jqObjPos = el$.offset(),
+                        jqObjLeft = jqObjPos.left,
+                        jqObjTop = jqObjPos.top,
+                        jqObjMarginTop = el$.css('margin-top'),
+                        jqObjWidth = el$.outerWidth() + 4,
+                        jqObjHeight = el$.outerHeight() + 2;
+
+                el$.before('<iframe id="' + cover_up_id + '" class="applet_cover_up" src="about:blank" width="' + jqObjWidth + '" height="' + jqObjHeight + '"></iframe>');
+
+                jq('#' + cover_up_id).css({
+                    display: 'block',
+                    position: 'fixed',
+                    width: jqObjWidth,
+                    height: jqObjHeight,
+                    marginTop: jqObjMarginTop,
+                    left: jqObjLeft,
+                    top: jqObjTop,
+                    background: 'transparent',
+                    border: 'none',
+                    outline: 'none'
+                });
+            }
+
+            function unCoverApplets(el$) {
+                el$.prev('iframe.applet_cover_up').detach();
+            }
+
+            function fadeInNav(el$) {
+                el$.find('> ul').show().addClass('open');
+            }
+
+            function fadeOutNav(el$) {
+                el$.find('> ul').hide().removeClass('open');
+            }
+
+            // give menus with submenus a class of 'more'
+            main_nav$.find('li ul, li li ul').closest('li').addClass('more');
+            main_nav$.find('li li ul').addClass('subnav');
+
+            // no fancy fades on hover
+            main_nav$.find('li.more').on('mouseover',
+                    function () {
+                        var li$ = $(this);
+                        fadeInNav(li$);
+                        li$.find('ul.subnav').each(function () {
+                            var sub$ = $(this);
+                            var offsetL = sub$.closest('ul').outerWidth();
+                            sub$.css({'left': offsetL + -25})
+                        });
+                        if (body$.hasClass('applet')) {
+                            coverApplet(li$.find('> ul'));
+                        }
+                    }
+            ).on('mouseout',
+                    function () {
+                        var li$ = $(this);
+                        fadeOutNav(li$);
+                        if (body$.hasClass('applet')) {
+                            unCoverApplets(li$.find('> ul'));
+                        }
+                    }
+            );
+
+            // clicking the "Logout" link sets the warning bar cookie to 'OPEN' so it's available if needed on next login
+            jq('#logout_user').click(function () {
+                Cookies.set('WARNING_BAR', 'OPEN', {path: '/'});
+                Cookies.set('NOTIFICATION_MESSAGE', 'OPEN', {path: '/'});
+            });
+
+        })();
+    </script>
+    <!-- end main_nav interactions -->
+
+    <div id="header" class="main_header">
+        <div class="pad">
+
+            <a id="header_logo" href="/app/template/Index.vm" style="display:none;">
+                <img class="logo_img" src="/images/logo.png" style="border:none;"> </a>
+
+        </div>
+    </div>  <!-- /header -->
+
+
+    <script type="text/javascript">
+
+        (function () {
+
+            var header_logo$ = $('#header_logo');
+
+            // adjust height of header if logo is taller than 65px
+            var hdr_logo_height = header_logo$.height();
+            if (hdr_logo_height > 65) {
+                jq('.main_header').height(hdr_logo_height + 10);
+            }
+
+            // adjust width of main nav if logo is wider than 175px
+            var hdr_logo_width = header_logo$.width();
+            if (hdr_logo_width > 175) {
+                jq('#main_nav').width(932 - hdr_logo_width - 20);
+            }
+
+            //
+            //var recent_proj_height = jq('#min_projects_list > div').height();
+            var recent_proj_height = 67;
+            //jq('#min_projects_list, #min_expt_list').height(recent_proj_height * 5).css({'min-width':349,'overflow-y':'scroll'});
+
+        })();
+
+        logged_in = true;
+
+        // initialize the advanced search method toggler
+        XNAT.app.searchMethodToggler = function (_parent) {
+
+            _parent = $$(_parent || 'body');
+
+            var INPUTS = 'input, select, textarea, :input',
+                    SEARCH_METHOD_CKBOXES = 'input.search-method',
+                    __searchGroups = _parent.find('div.search-group'),
+                    __searchMethodInputs = _parent.find(SEARCH_METHOD_CKBOXES);
+
+            // disable 'by-id' search groups by default
+            __searchGroups.filter('.by-id').addClass('disabled').find(INPUTS).not(SEARCH_METHOD_CKBOXES).changeVal('').prop('disabled', true).addClass('disabled');
+
+            // enable 'by-criteria' search groups by default
+            __searchGroups.filter('.by-criteria').removeClass('disabled').find(INPUTS).prop('disabled', false).removeClass('disabled');
+
+            // check 'by-criteria' checkboxes
+            __searchMethodInputs.filter('.by-criteria').prop('checked', true);
+
+            // don't add multiple click handlers
+            __searchMethodInputs.off('click');
+
+            // toggle the search groups
+            __searchMethodInputs.on('click', function () {
+
+                var method = this.value,
+                        isChecked = this.checked;
+
+                __searchGroups.addClass('disabled').find(INPUTS).not(SEARCH_METHOD_CKBOXES).changeVal('').prop('disabled', true).addClass('disabled');
+
+                __searchGroups.filter('.' + method).removeClass('disabled').find(INPUTS).prop('disabled', false).removeClass('disabled');
+
+                // update the radio buttons/checkboxes
+                __searchMethodInputs.prop('checked', false);
+                __searchMethodInputs.filter('.' + method).prop('checked', true);
+                chosenUpdate();
+            });
+        };
+
+    </script>
+
+    <script src="/scripts/timeLeft.js"></script>
+
+    <div id="tp_fm"></div>
+
+    <!-- END: xnat-templates/navigations/DefaultTop.vm -->
+
+    <!-- /xnat-templates/navigations/DefaultLeft.vm -->
+
+    <div id="l_tv" style="display:none;"></div>
+
+    <script type="text/javascript">
+        //build element_array
+        var lTV = new LeftBarTreeView({treeview: "l_tv"});
+
+        lTV.loadNodeData = function (node, fnLoadComplete) {
+            var tabReqObject = node.data;
+            tabReqObject.label = node.label;
+            tabReqObject.node = node;
+            if (tabReqObject.URL != undefined) {
+                window.location = serverRoot + "/app/template/Search.vm/node/" + tabReqObject.ID;
+            }
+            else {
+                if (tabReqObject.ID != undefined && tabReqObject.ID == "ss") {
+                    var callback = {
+                        cache: false, // Turn off caching for IE
+                        success: function (oResponse) {
+                            var oResults = eval("(" + oResponse.responseText + ")");
+                            if ((oResults.ResultSet.Result) && (oResults.ResultSet.Result.length)) {
+                                for (var ssC = 0; ssC < oResults.ResultSet.Result.length; ssC++) {
+                                    var cpNode = new YAHOO.widget.TextNode({
+                                        label: oResults.ResultSet.Result[ssC].brief_description,
+                                        ID: "ss." + oResults.ResultSet.Result[ssC].id,
+                                        SS_ID: oResults.ResultSet.Result[ssC].id,
+                                        URL: serverRoot + '/REST/search/saved/' + oResults.ResultSet.Result[ssC].id + '',
+                                        TITLE: oResults.ResultSet.Result[ssC].description
+                                    }, oResponse.argument.node, false);
+                                }
+                            }
+                            oResponse.argument.fnLoadComplete();
+                        },
+                        failure: function (oResponse) {
+                            oResponse.argument.fnLoadComplete();
+                        },
+                        argument: {"node": node, "fnLoadComplete": fnLoadComplete}
+                    };
+
+                    //YAHOO.util.Connect.asyncRequest('GET',this.obj.URL,this.initCallback,null,this);
+                    YAHOO.util.Connect.asyncRequest('GET', serverRoot + '/REST/search/saved?XNAT_CSRF=' + window.csrfToken + '&format=json&stamp=' + (new Date()).getTime(), callback, null);
+                }
+                else {
+                    fnLoadComplete();
+                }
+            }
+        }
+
+        lTV.init();
+
+    </script>
+    <!-- end /xnat-templates/navigations/DefaultLeft.vm -->
+
+    <div id="breadcrumbs"></div>
+    <script src="/scripts/xnat/ui/breadcrumbs.js"></script>
+    <script language="javascript">
+
+        window.isProjectPage = (XNAT.data.context.xsiType === 'xnat:projectData');
+
+        // wrap it up to keep things
+        // out of global scope
+        (function () {
+
+            var crumbs = [];
+
+
+            XNAT.ui.breadcrumbs.render('#breadcrumbs', crumbs);
+
+
+        })();
+
+    </script>
+
+    <div id="layout_content2" style="display:none;">Loading...</div>
+    <div id="layout_content">
+        <!--BEGIN SCREEN CONTENT -->
+
+        <jsp:doBody/>
+
+        <!-- END SCREEN CONTENT -->
+    </div>
+
+    <div id="mylogger"></div>
+
+
+    <!-- path: xnat-templates/navigations/htmlClose -->
+</div><!-- /page_wrapper -->
+
+<div id="xnat_power">
+    <a target="_blank" href="http://www.xnat.org/" style=""><img src="/images/xnat_power_small.png"></a>
+</div>
+
+<script type="text/javascript">
+
+    loadjs(scriptUrl('xnat/event.js'), function () {
+
+        // shift-click the header or footer XNAT logo to ENABLE debug mode
+        // alt-shift-click to DISABLE debug mode
+        XNAT.event.click('#header_logo, #xnat_power > a')
+                .shiftKey(function (e) {
+                    e.preventDefault();
+                    window.location.hash = 'debug=on'
+                    window.location.reload();
+                })
+                .altShift(function (e) {
+                    e.preventDefault();
+                    window.location.hash = 'debug=off'
+                    window.location.reload();
+                });
+
+    })
+
+</script>
+<script type="text/javascript" src="/scripts/footer.js"></script>
+</body>
+</html>
diff --git a/src/main/webapp/WEB-INF/views/spawner/element.jsp b/src/main/webapp/WEB-INF/views/spawner/element.jsp
new file mode 100644
index 0000000000000000000000000000000000000000..3954d9e9459d1388d87086a69dd661a5667135ac
--- /dev/null
+++ b/src/main/webapp/WEB-INF/views/spawner/element.jsp
@@ -0,0 +1,30 @@
+<%@ page contentType="text/html" pageEncoding="UTF-8" %>
+<%@ taglib prefix="sp" tagdir="/WEB-INF/tags/spawner" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<jsp:useBean id="elementId" scope="request" type="java.lang.String"/>
+
+<c:set var="title" value="Spawner Element: ${elementId}"/>
+<sp:layout title="${title}">
+    <h3 style="margin:0 0 15px 0;">${title}</h3>
+
+    <div style="margin:15px 0;max-width:696px;min-width:400px;">
+        Make your desired changes to the spawner element and click <b>Save</b> when completed.
+    </div>
+
+<script type="text/javascript">
+    var spawnerElement = $.ajax({
+        type : "GET",
+        url:serverRoot+"/xapi/spawner/${elementId}?XNAT_CSRF=" + window.csrfToken,
+        cache: false,
+        async: true,
+        context: this,
+        dataType: "yaml"
+    });
+    spawnerElement.done( function( data, textStatus, jqXHR ) {
+        if (typeof data.ResultSet !== "undefined" && typeof data.ResultSet.Result !== "undefined") {
+            alert(data);
+        }
+    });
+
+</script>
+</sp:layout>
\ No newline at end of file
diff --git a/src/main/webapp/WEB-INF/views/spawner/elements.jsp b/src/main/webapp/WEB-INF/views/spawner/elements.jsp
new file mode 100644
index 0000000000000000000000000000000000000000..bea671ed2573041d7d256d8de89739360fd3d41e
--- /dev/null
+++ b/src/main/webapp/WEB-INF/views/spawner/elements.jsp
@@ -0,0 +1,255 @@
+<!-- BEGIN views/spawner/elements.jsp -->
+<%@ page contentType="text/html" pageEncoding="UTF-8" %>
+<%@ taglib prefix="sp" tagdir="/WEB-INF/tags/spawner" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<jsp:useBean id="elements" scope="request" type="java.util.List"/>
+
+<sp:layout title="Spawner Elements">
+    <h3 style="margin:0 0 15px 0;">Spawner Elements</h3>
+
+    <div style="margin:15px 0;max-width:696px;min-width:400px;">
+        The Spawner is XNAT's system for creating and updating dynamically configurable user interface elements.
+    </div>
+
+    <div class="yui-skin-sam">
+
+        <style type="text/css" scoped="scoped">
+            #automation-events-scripts .yui-content > div {
+                padding: 2px;
+            }
+
+            td.edit:hover {
+                cursor: pointer
+            }
+
+            td.edit:hover a,
+            td.actions a:hover {
+                text-decoration: underline
+            }
+
+            td.edit a,
+            td.actions a {
+                display: inline-block;
+                padding: 2px 8px;
+            }
+
+            table.edit-script {
+                margin-bottom: -4px;
+            }
+
+            table.edit-script td {
+                padding: 4px 2px;
+                vertical-align: baseline;
+            }
+        </style>
+
+        <script type="text/javascript" src="/scripts/xnat/app/siteEventsManager.js"></script>
+        <script type="text/javascript" src="scripts/uploaders/AutomationBasedUploader.js"></script>
+
+        <div id="addEventHandler" class="html-template">
+            <table>
+                <tr>
+                    <td><label for="select_event" class="required"><strong>Event:</strong><i>*</i></label></td>
+                    <td><select id="select_event" name="event" class="event"></select></td>
+                </tr>
+                <tr>
+                    <td><label for="select_scriptId" class="required"><strong>Script ID:</strong><i>*</i></label></td>
+                    <td><select id="select_scriptId" name="scriptId" class="scriptId"></select></td>
+                </tr>
+                <tr>
+                    <td><label for="description"><strong>Description:</strong></label></td>
+                    <td><input id="description" name="description" class="description" type="text" size="40"></td>
+                </tr>
+            </table>
+        </div>
+
+        <!-- SITE EVENTS TABLE ROW TEMPLATE -->
+        <table id="event-row-template" class="html-template">
+            <tbody>
+            <tr class="highlight events-list">
+                <td class="site-event-id">
+                </td>
+                <td class="site-event-label">
+                </td>
+                <td class="workflow-label"></td>
+                <td class="actions" style="text-align:center;white-space:nowrap;">
+                </td>
+            </tr>
+            </tbody>
+        </table>
+
+        <table id="script-row-template" class="html-template">
+            <tbody>
+            <tr class="highlight script-list" data-script-id="__SCRIPT_ID__">
+                <td class="edit">
+                </td>
+                <td class="edit" data-action="editScript"></td>
+                <td class="actions" style="text-align:center;white-space:nowrap;">
+                </td>
+            </tr>
+            </tbody>
+        </table>
+
+        <div class="yui-content">
+
+            <table id="elements-table" class="xnat-table" style="width: 100%;">
+
+                <thead>
+                <tr><th width="40%">Element</th>
+                    <th width="40%">Description</th>
+                    <th width="20%">&nbsp;</th>
+                </tr></thead>
+
+                <tbody>
+                <c:forEach var="element" items="${elements}">
+                    <tr class="highlight events-list">
+                        <td class="site-event-id">
+                            <a href="elements/${element.elementId}" class="edit-event" data-action="editEvent"
+                               title="edit existing event">${element.label}</a>
+                        </td>
+                        <td class="site-event-label">
+                                ${element.description}
+                        </td>
+                        <td class="actions" style="text-align:center;white-space:nowrap;">
+                            <a href="#!" class="delete-event" data-action="deleteEvent"
+                               title="delete existing event">delete</a>
+                        </td>
+                    </tr>
+                </c:forEach>
+                </tbody>
+
+            </table>
+
+        </div>
+
+        <script type="text/javascript">
+
+            // YUI tab simulator using jQuery for event handling for '.bogus-tabs'
+            // using 'on' to handle any tabs that may be added dynamically
+            jq('.yui-navset.bogus-tabs').each(function () {
+
+                var __navset = jq(this),
+                        TABS = '> ul.yui-nav > li',
+                        CONTENTS = '> .yui-content > div',
+                        selected = null;
+
+                __navset.on('click', TABS + '> a', function (e) {
+
+                    e.preventDefault();
+
+                    var __link = jq(this),
+                            __tab = __link.closest('li');
+
+                    selected = __link.attr('href'); // safer to use jQuery to get 'href' value
+
+                    __navset.find(TABS).removeClass('selected');
+                    __tab.addClass('selected');
+
+                    __navset.find(CONTENTS).addClass('yui-hidden');
+                    __navset.find(selected).removeClass('yui-hidden');
+
+                });
+
+            });
+
+        </script>
+
+        <!-- SITE EVENTS DIALOG TEMPLATE -->
+        <div id="site-events-template" class="html-template">
+            <p>Select a Workflow or enter a new Event name to define an Event to use with project and site Event
+                Handlers:</p>
+            <table>
+                <tr>
+                    <td><label for="workflow-event-id-menu" class="required"><strong>Workflow:</strong><i>*</i></label>
+                    </td>
+                    <td>
+
+                        <div id="workflow-event-id-container">
+                            ## workflow events menu
+                        </div>
+                        ## <select id="workflow-event-id-menu" name="event_id" class="event_id active">
+                        ##
+                        <option data-label="__WORKFLOW_EVENT_LABEL__" data-id="__WORKFLOW_EVENT_ID__"
+                                value="__WORKFLOW_EVENT_ID__">__WORKFLOW_EVENT_LABEL__
+                        </option>
+                        ## </select>
+
+                    </td>
+                </tr>
+                <tr>
+                    <td></td>
+                    <td>
+                        <div id="custom-event-id" style="padding:10px 0;">
+                            <input type="checkbox" id="custom-event-checkbox">
+                            <label for="custom-event-checkbox">Or enter a custom Event id:</label>
+                            <br>
+                            <input type="text" id="custom-event-input" class="event_id"
+                                   placeholder="(enter custom Event id)" disabled>
+                        </div>
+                    </td>
+                </tr>
+                ##
+                <tr>
+                    ##
+                    <td><label for="scriptId" class="required"><strong>Script ID:</strong><i>*</i></label></td>
+                    ##
+                    <td><select id="scriptId" name="scriptId" class="scriptId"></select></td>
+                    ##
+                </tr>
+                <tr>
+                    <td><label for="events-event-label"><strong>Label:</strong></label></td>
+                    <td>
+                        <input id="events-event-label" name="event_label" class="event_label" type="text" size="40">
+                    </td>
+                </tr>
+                <tr>
+                    <td></td>
+                    <td>
+                        <small>Label not required. If no label is entered, it will be the same as the ID.</small>
+                    </td>
+                </tr>
+            </table>
+        </div>
+
+        <!-- SCRIPT EDITOR TEMPLATE -->
+        <div id="script-editor-template" class="html-template">
+            <input type="hidden" name="id" class="id" value="">
+            <input type="hidden" name="scriptId" class="scriptId" value="">
+            <input type="hidden" name="language" class="language" value="">
+            <input type="hidden" name="languageVersion" class="languageVersion" value="">
+            <input type="hidden" name="timestamp" class="timestamp" value="">
+            <table class="edit-script" border="0" cellspacing="0">
+                <tr>
+                    <td><b>Script ID: </b>&nbsp;</td>
+                    <td>
+                        <b class="script-id-text"></b>
+                        <input type="text" name="script-id-input" class="script-id-input" size="30" value="">
+                    </td>
+                </tr>
+                <tr>
+                    <td><b>Description: </b>&nbsp;</td>
+                    <td><input type="text" name="script-description" class="script-description" size="80" value=""></td>
+                </tr>
+                <tr>
+                    <td><b>Script Version: </b>&nbsp;</td>
+                    <td><select id="script-version" class="script-version">
+                        <option value="!">Select a Version</option>
+                    </select></td>
+                </tr>
+            </table>
+            <br>
+            <div class="editor-wrapper" style="width:840px;height:482px;position:relative;">
+                <!-- the '.editor-content' div gets replaced when the script content is loaded -->
+                <div class="editor-content"
+                     style="position:absolute;top:0;right:0;bottom:0;left:0;border:1px solid #ccc;"></div>
+            </div>
+        </div><!-- /#script-editor-template -->
+
+        <script type="text/javascript" src="/scripts/lib/ace/ace.js"></script>
+        <script type="text/javascript" src="/scripts/xnat/app/automation.js"></script>
+        <script type="text/javascript" src="/scripts/xnat/app/scriptEditor.js"></script>
+
+    </div>
+
+</sp:layout>
+<!-- END views/spawner/elements.jsp -->
\ No newline at end of file
diff --git a/src/main/webapp/resources/ui/skins/default/skin.props b/src/main/webapp/resources/ui/skins/default/skin.props
new file mode 100755
index 0000000000000000000000000000000000000000..59eecbe213ab4d3931579d7b71d4cc5e9161da8a
--- /dev/null
+++ b/src/main/webapp/resources/ui/skins/default/skin.props
@@ -0,0 +1,29 @@
+# These are the default properties  
+bgcolor = #ffffff 
+sansSerifFonts = verdana,geneva,helvetica 
+#formLabelColor = #b3cc99 
+#formFieldColor = #b3bb99 
+#labelColor = #b3cc99 
+#dataColor = #b3bb99 
+formLabelColor = #ffffff 
+formFieldColor = #ffffff 
+labelColor = #ffffff 
+dataColor = #ffffff 
+#vlink = #330066 
+#alink = #663399 
+vlink = #000000 
+alink = #000000  
+tableCellSpacing = 0 
+tableCellPadding = 0  
+menuColor = #000000  
+buttonAlignment = left 
+buttonColor = #DDDDDD  
+# Images alertImage = alert1.gif 
+logo = logo.jpg 
+poweredByImage = powered_by_tambora.gif 
+line = line.gif  
+#darkColor = #000088 
+#lightColor = #DDDDDD 
+darkColor = #000066 
+lightColor = #333399 
+tableColor = #AAAAAA   
\ No newline at end of file
diff --git a/src/main/webapp/resources/ui/skins/default/skin.txt b/src/main/webapp/resources/ui/skins/default/skin.txt
new file mode 100755
index 0000000000000000000000000000000000000000..081a8930bd907dee5d0de030c6eb6cc8d5746a34
Binary files /dev/null and b/src/main/webapp/resources/ui/skins/default/skin.txt differ
diff --git a/src/main/webapp/scripts/themeManagement.js b/src/main/webapp/scripts/themeManagement.js
new file mode 100644
index 0000000000000000000000000000000000000000..4e8d49929663710e6f765a2281773647d01082ab
--- /dev/null
+++ b/src/main/webapp/scripts/themeManagement.js
@@ -0,0 +1,123 @@
+/*
+ * org.nrg.xnat.turbine.modules.screens.ManageProtocol
+ * XNAT http://www.xnat.org
+ * Copyright (c) 2013, Washington University School of Medicine
+ * All Rights Reserved
+ *
+ * Released under the Simplified BSD.
+ *
+ * Author: Justin Cleveland <clevelandj@wustl.edu>
+ * Last modified 3/17/2016 3:33 PM
+ */
+var themeUrl = XNAT.url.rootUrl('xapi/theme');
+var s = '/', q = '?', a = '&';
+var csrf = 'XNAT_CSRF='+window.csrfToken;
+$('#titleAppName').text(XNAT.app.siteId);
+var currentTheme = $('#currentTheme');
+var themeSelector = $('#themeSelection');
+var uploadForm = document.getElementById('uploadThemeForm');
+var themeUploader = document.getElementById('themeFileUpload');
+var themeUploadSubmit = document.getElementById('submitThemeUploadButton');
+var selectedTheme = null;
+function populateThemes(){
+    getCurrentTheme(getAvailableThemes);
+};
+function getCurrentTheme(callback){
+    var role = 'global';
+    $.get(themeUrl+s+role+q+csrf, null, function (data){
+        themeSelector.empty();
+        selectedTheme = data.name?data.name:'None';
+        currentTheme.text(selectedTheme);
+        if(typeof callback === 'function'){
+            callback(data.name);
+        }
+    }, 'json');
+};
+function getAvailableThemes(selected){
+    $.get(themeUrl+q+csrf, null, function (data){
+        themeSelector.empty();
+        addThemeOptions(data, selected);
+    }, 'json');
+};
+function addThemeOptions(newThemeOptions, selected){
+    if(Array.isArray(newThemeOptions)) {
+        $(newThemeOptions).each(function (i, opt) {
+            var select = '';
+            if (selected == opt.value) {
+                select = ' selected="selected"';
+            }
+            themeSelector.append('<option value="'+opt.value+'"'+select+'>'+opt.label+'</option>');
+        });
+    }
+};
+function selectTheme(themeToSelect){
+    if(themeToSelect && typeof themeToSelect === 'string'){
+        themeSelector.val(themeToSelect);
+    }
+};
+function setTheme(){
+    xmodal.confirm({
+        content: 'Theme selection appearances may not fully take effect until users log out, clear their browser cache and log back in.'+
+        '<br><br>Are you sure you wish to change the global theme?',
+        action: function(){
+            $.put(themeUrl+s+encodeURI(themeSelector.val())+q+csrf, null, function(data){
+                console.log(data);
+                populateThemes()
+            });
+        }
+    });
+};
+function removeTheme(){
+    xmodal.confirm({
+        content: 'Are you sure you wish to delete the selected theme?',
+        action: function(){
+            $.delete(themeUrl+s+encodeURI(themeSelector.val())+q+csrf, null, function(data){
+                console.log(data);
+                populateThemes();
+            });
+        }
+    });
+};
+
+/*** Theme Package Upload Functions ***/
+uploadForm.action = themeUrl+q+csrf;
+uploadForm.onsubmit = function(event) {
+    event.preventDefault();
+    $(themeUploadSubmit).text('Uploading...');
+    $(themeUploadSubmit).attr('disabled', 'disabled');
+    var files = themeUploader.files;
+    var formData = new FormData();
+    var uploaded = false;
+    for (var i = 0; i < files.length; i++) {
+        var file = files[i];
+        if (!file.type.match('zip.*')) {
+            continue;
+        }
+        formData.append('themePackage', file, file.name); // formData.append('themes[]', file, file.name);
+        var xhr = new XMLHttpRequest();
+        xhr.open('POST', uploadForm.action, true);
+        xhr.onload = function () {
+            if (xhr.status !== 200) {
+                console.log(xhr.statusText);
+                console.log(xhr.responseText);
+                xmodal.message('Upload Error', 'There was a problem uploading your theme package.<br>Server responded with: '+xhr.statusText);
+            }
+            $(themeUploadSubmit).text('Upload');
+            $(themeUploadSubmit).removeAttr('disabled');
+            var newThemeOptions = $.parseJSON(xhr.responseText);
+            var selected;
+            if(newThemeOptions[0]){
+                selected = newThemeOptions[0].value;
+            }
+            addThemeOptions(newThemeOptions, selected);
+        };
+        xhr.send(formData);
+        uploaded = true;
+    }
+    if(!uploaded){
+        xmodal.message('Nothing Uploaded', 'No valid theme package files were selected for upload.');
+        $(themeUploadSubmit).text('Upload');
+        $(themeUploadSubmit).removeAttr('disabled');
+    }
+};
+$(populateThemes);  // ...called once DOM is fully loaded "ready"
diff --git a/src/main/webapp/scripts/xnat/ui/popup.js b/src/main/webapp/scripts/xnat/ui/popup.js
index 7056475ce4ff77584c18b91716a282667efedf92..1da45567cee591bf0afd2be9431ef0420ec0da2f 100644
--- a/src/main/webapp/scripts/xnat/ui/popup.js
+++ b/src/main/webapp/scripts/xnat/ui/popup.js
@@ -7,9 +7,8 @@ var XNAT = getObject(XNAT||{});
 (function(XNAT){
 
     var popup,
-        window = this,
-        xurl = getObject(XNAT.url||{});
-
+        window = this;
+    
     // base popup function
     function popupCentered( /* url, title, w, h, y, params */ ) {
 
@@ -91,8 +90,8 @@ var XNAT = getObject(XNAT||{});
 
     // is the current window or 'loc' an XNAT popup?
     function isPopup(loc){
-        loc = xurl.splitUrl(loc || window.location.href);
-        return (loc.base.indexOf('popup/true') > -1 || xurl.getParam('popup') === 'true')
+        loc = XNAT.url.splitUrl(loc || window.location.href);
+        return (loc.base.indexOf('popup/true') > -1 || XNAT.url.getQueryStringValue('popup') === 'true')
     }
     // save popup status for global usage
     window.isPopup = isPopup();
@@ -101,10 +100,10 @@ var XNAT = getObject(XNAT||{});
     // process a url to add the 'popup' parts
     function setupUrl(fullUrl){
 
-        var urlParts = xurl.splitUrl(fullUrl),
-            newUrl = xurl.updateBase(fullUrl, urlParts.base.replace(/(\/popup\/(false|true))|(\/+$)/g,'') + '/popup/true');
+        var urlParts = XNAT.url.splitUrl(fullUrl),
+            newUrl = XNAT.url.updateBase(fullUrl, urlParts.base.replace(/(\/popup\/(false|true))|(\/+$)/g,'') + '/popup/true');
 
-        return xurl.addQueryString(newUrl, '?popup=true')
+        return XNAT.url.addQueryString(newUrl, '?popup=true')
 
     }
     popup.setupUrl = setupUrl;
diff --git a/src/main/webapp/xnat-templates/navigations/HeaderIncludes.vm b/src/main/webapp/xnat-templates/navigations/HeaderIncludes.vm
index 9b6ebd7fb3c2371b1eca7212ce49325c740b8ead..d7046f0bf41af35a976d23d49adee590f8cd93d8 100755
--- a/src/main/webapp/xnat-templates/navigations/HeaderIncludes.vm
+++ b/src/main/webapp/xnat-templates/navigations/HeaderIncludes.vm
@@ -15,6 +15,13 @@
 <meta http-equiv="expires" content="-1">
 <meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT">
 
+#if ($themedRedirect)
+<!-- As a backup, reload the appropriate page if a global theme style specifies a redirect -->
+<script type="text/javascript">
+    window.location = "$content.getURI("")$themedRedirect";
+</script>
+#end
+
 <!-- load polyfills before ANY other JavaScript -->
 <script type="text/javascript" src="$content.getURI('scripts/polyfills.js')"></script>
 
@@ -286,4 +293,14 @@
 <script type="text/javascript" src="$content.getURI('scripts/xnat/ui/popup.js')"></script>
 <script type="text/javascript" src="$content.getURI('scripts/xnat/ui/dialog.js')"></script>
 
+#if ($themedScript)
+<!-- Load active global theme script -->
+<script type="text/javascript" src="$content.getURI("")$themedScript"></script>
+#end
+
+#if ($themedStyle)
+<!-- Load active global theme style -->
+<link rel="stylesheet" type="text/css" href="$content.getURI("")$themedStyle">
+#end
+
 #addGlobalCustomScreens("header")
diff --git a/src/main/webapp/xnat-templates/screens/XDATScreen_themes.vm b/src/main/webapp/xnat-templates/screens/XDATScreen_themes.vm
index f9d016eda9441f8c2ea6f97ac5cd1799f05038ca..cf8dab12ced971d3f65e7ea985117ed427d06743 100644
--- a/src/main/webapp/xnat-templates/screens/XDATScreen_themes.vm
+++ b/src/main/webapp/xnat-templates/screens/XDATScreen_themes.vm
@@ -25,88 +25,6 @@
     </form>
 </div>
 
-<script>
-    var themeUrl = "$content.getURI('/data/theme')";
-    var q = '?', a = '&';
-    var csrf = 'XNAT_CSRF='+window.csrfToken;
-	$('#titleAppName').text(XNAT.app.siteId);
-    var currentTheme = $('#currentTheme');
-    var themeSelector = $('#themeSelection');
-    var uploadForm = document.getElementById('uploadThemeForm');
-    var themeUploader = document.getElementById('themeFileUpload');
-    var themeUploadSubmit = document.getElementById('submitThemeUploadButton');
-    var selectedTheme = null;
-	function populateThemes(themeToSelect){
-        $.get(themeUrl+q+csrf, null, function (data){
-            themeSelector.empty();
-            $(data.themeOptions).each(function(i, opt) {
-                var selected = '', selectedTheme = null;
-                if(data.theme == opt.value){
-                    selectedTheme = data.theme;
-                    selected = ' selected="selected"';
-                    currentTheme.text(selectedTheme?selectedTheme:'None');
-                }
-                themeSelector.append('<option value="'+opt.value+'"'+selected+'>'+opt.label+'</option>');
-            });
-            if(themeToSelect && typeof themeToSelect === 'string'){
-                themeSelector.val(themeToSelect);
-            }
-        }, 'json');
-	}
-    function setTheme(){
-        xmodal.confirm({
-            content: 'Theme selection appearances may not fully take effect until users log out, clear their browser cache and log back in.'+
-                     '<br><br>Are you sure you wish to change the global theme?',
-            action: function(){
-                $.put(themeUrl+q+'theme='+encodeURI(themeSelector.val())+a+csrf, null, populateThemes);
-            }
-        });
-    }
-	function removeTheme(){
-		xmodal.confirm({
-			content: 'Are you sure you wish to delete the selected theme?',
-			action: function(){
-                $.delete(themeUrl+q+'theme='+encodeURI(themeSelector.val())+a+csrf, null, populateThemes);
-			}
-		});
-	}
-
-    /*** Theme Package Upload Functions ***/
-    uploadForm.action = themeUrl+q+csrf;
-    uploadForm.onsubmit = function(event) {
-        event.preventDefault();
-        $(themeUploadSubmit).text('Uploading...');
-        $(themeUploadSubmit).attr('disabled', 'disabled');
-        var files = themeUploader.files;
-        var formData = new FormData();
-        var uploaded = false;
-        for (var i = 0; i < files.length; i++) {
-            var file = files[i];
-            if (!file.type.match('zip.*')) {
-                continue;
-            }
-            formData.append('themes[]', file, file.name);
-            var xhr = new XMLHttpRequest();
-            xhr.open('POST', uploadForm.action, true);
-            xhr.onload = function () {
-                if (xhr.status !== 200) {
-                    xmodal.message('Upload Error', xhr.responseText);
-                }
-                var uploadedTheme = xhr.responseText;
-                $(themeUploadSubmit).text('Upload');
-                $(themeUploadSubmit).removeAttr('disabled');
-                populateThemes(uploadedTheme);
-            };
-            xhr.send(formData);
-            uploaded = true;
-        }
-        if(!uploaded){
-            xmodal.message('Nothing Uploaded', 'No valid theme package files were selected for upload.');
-            $(themeUploadSubmit).text('Upload');
-            $(themeUploadSubmit).removeAttr('disabled');
-        }
-    }
-	$(populateThemes);  // ...called once DOM is fully loaded "ready"
-</script>
+<script type="text/javascript" src="$content.getURI('scripts/themeManagement.js')"></script>
 
 <!-- End XDATScreen_themes.vm -->
\ No newline at end of file
diff --git a/src/main/webapp/xnat-templates/screens/xnat_imageSessionData/xnat_imageSessionData_details.vm b/src/main/webapp/xnat-templates/screens/xnat_imageSessionData/xnat_imageSessionData_details.vm
index fdbdbfaf5c193cd56ec200c3af124ae8a8bd2776..ad1545f512fbda89b19b64a3e6f36aedb464f90c 100644
--- a/src/main/webapp/xnat-templates/screens/xnat_imageSessionData/xnat_imageSessionData_details.vm
+++ b/src/main/webapp/xnat-templates/screens/xnat_imageSessionData/xnat_imageSessionData_details.vm
@@ -98,19 +98,4 @@
     </tr>
 </table>
 #end
-<button type="button" id="launchScript">Launch Script</button>
-<script type="text/javascript">
-
-    scriptCallback={
-        success:function(){alert("Script launched.")},
-        failure:function(){alert("Script failed to launch.")},
-        cache:false, // Turn off caching for IE
-        scope:this
-    }
-
-    jQuery('#launchScript').click(function(event){
-        var params="sessionId=" + "${om.getId()}" + "&mountIn=" + "/data/input" + "&mountOut=" + "/data/output" + "&imageId=" + "kelseym/pydicom";
-        YAHOO.util.Connect.asyncRequest('POST',serverRoot +"/xapi/containers/launch/script/${om.getId()}?XNAT_CSRF=" + csrfToken,scriptCallback,params,this);
-    });
-</script>
 <!--END xnat_imageSessionData_details.vm -->
\ No newline at end of file