diff --git a/src/main/java/org/nrg/xdat/om/base/BaseWrkWorkflowdata.java b/src/main/java/org/nrg/xdat/om/base/BaseWrkWorkflowdata.java
old mode 100644
new mode 100755
index 3ff6ebbfb585fe61ce1198a8ea5a85aabfcf0165..4ce46d05245d8b25db280cb6e2e67d1c036c08ae
--- a/src/main/java/org/nrg/xdat/om/base/BaseWrkWorkflowdata.java
+++ b/src/main/java/org/nrg/xdat/om/base/BaseWrkWorkflowdata.java
@@ -18,12 +18,11 @@ import org.nrg.xdat.om.base.auto.AutoWrkWorkflowdata;
 import org.nrg.xft.ItemI;
 import org.nrg.xft.XFTItem;
 import org.nrg.xft.db.PoolDBUtils;
+import org.nrg.xft.event.XftEventService;
 import org.nrg.xft.event.EventMetaI;
-import org.nrg.xft.event.ReactorEventUtils;
 import org.nrg.xft.event.EventUtils.CATEGORY;
 import org.nrg.xft.event.EventUtils.TYPE;
 import org.nrg.xft.event.WorkflowStatusEvent;
-import org.nrg.xft.event.XftItemEvent;
 import org.nrg.xft.event.persist.PersistentWorkflowI;
 import org.nrg.xft.exception.ElementNotFoundException;
 import org.nrg.xft.exception.XFTInitException;
@@ -389,12 +388,17 @@ public class BaseWrkWorkflowdata extends AutoWrkWorkflowdata implements Persiste
 	 */
 	@Override
 	public void postSave() throws Exception {
+		postSave(true);
+	}
+	
+	@Override
+	public void postSave(boolean triggerEvent) throws Exception {
 		super.postSave();
 		
 		if(getStatus()!=null){			
 			//status changed
-			if(this.getWorkflowId()!=null){
-				ReactorEventUtils.triggerEvent(WorkflowStatusEvent.class.getName() + 
+			if(this.getWorkflowId()!=null && triggerEvent){
+				XftEventService.getService().triggerEvent(WorkflowStatusEvent.class.getName() + 
 						((this.getStatus()!=null) ? "." + this.getStatus() : ""),new WorkflowStatusEvent(this));
 			}
 		}
diff --git a/src/main/java/org/nrg/xdat/om/base/BaseXnatProjectdata.java b/src/main/java/org/nrg/xdat/om/base/BaseXnatProjectdata.java
old mode 100644
new mode 100755
index 25d4f1e0b602352e07e74c2b0e3a0be304ee7673..dd67968ffad7a2a5e82d07b5c49d30df452f3ade
--- a/src/main/java/org/nrg/xdat/om/base/BaseXnatProjectdata.java
+++ b/src/main/java/org/nrg/xdat/om/base/BaseXnatProjectdata.java
@@ -36,7 +36,6 @@ import org.nrg.xdat.security.ElementSecurity;
 import org.nrg.xdat.security.SecurityValues;
 import org.nrg.xdat.security.UserGroupI;
 import org.nrg.xdat.security.XdatStoredSearch;
-import org.nrg.xdat.security.helpers.Features;
 import org.nrg.xdat.security.helpers.Groups;
 import org.nrg.xdat.security.helpers.Permissions;
 import org.nrg.xdat.security.helpers.Users;
@@ -1301,7 +1300,7 @@ public class BaseXnatProjectdata extends AutoXnatProjectdata implements Archivab
             }
 
             try {
-                ReactorEventUtils.triggerEvent(new XftItemEvent(Groups.getGroupDatatype(), XftItemEvent.UPDATE));
+            	XftEventService.getService().triggerEvent(new XftItemEvent(Groups.getGroupDatatype(), XftItemEvent.UPDATE));
             } catch (Exception e1) {
                 logger.error("", e1);
             }
diff --git a/src/main/java/org/nrg/xnat/archive/DicomZipImporter.java b/src/main/java/org/nrg/xnat/archive/DicomZipImporter.java
index 996c0b954a9ca64ddf0ff2d3fc6ce76f81210f32..b2267f4daba7e4e24acb41108842c813381d0758 100644
--- a/src/main/java/org/nrg/xnat/archive/DicomZipImporter.java
+++ b/src/main/java/org/nrg/xnat/archive/DicomZipImporter.java
@@ -25,12 +25,14 @@ import org.nrg.xdat.om.XnatProjectdata;
 import org.nrg.xft.security.UserI;
 import org.nrg.xnat.DicomObjectIdentifier;
 import org.nrg.xnat.helpers.ZipEntryFileWriterWrapper;
+import org.nrg.xnat.restlet.actions.importer.ImporterHandler;
 import org.nrg.xnat.restlet.actions.importer.ImporterHandlerA;
 import org.nrg.xnat.restlet.util.FileWriterWrapperI;
 
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 
+@ImporterHandler(handler = "DICOM-zip", allowCallsWithoutFiles = false)
 public final class DicomZipImporter extends ImporterHandlerA {
     private final InputStream in;
     private final Object listenerControl;
diff --git a/src/main/java/org/nrg/xnat/archive/GradualDicomImporter.java b/src/main/java/org/nrg/xnat/archive/GradualDicomImporter.java
index ba47d300e7bb35077603aaddbe2ccb3e1fb701f1..621f883a34f2ff80bd632ebd65149ee55f76d3f4 100644
--- a/src/main/java/org/nrg/xnat/archive/GradualDicomImporter.java
+++ b/src/main/java/org/nrg/xnat/archive/GradualDicomImporter.java
@@ -48,6 +48,7 @@ import org.nrg.xnat.helpers.prearchive.PrearcDatabase.Either;
 import org.nrg.xnat.helpers.prearchive.PrearcUtils.SessionFileLockException;
 import org.nrg.xnat.helpers.prearchive.SessionData;
 import org.nrg.xnat.helpers.uri.URIManager;
+import org.nrg.xnat.restlet.actions.importer.ImporterHandler;
 import org.nrg.xnat.restlet.actions.importer.ImporterHandlerA;
 import org.nrg.xnat.restlet.util.FileWriterWrapperI;
 import org.nrg.xnat.turbine.utils.ArcSpecManager;
@@ -63,6 +64,7 @@ import java.util.concurrent.Callable;
 
 @SuppressWarnings("ThrowFromFinallyBlock")
 @Service
+@ImporterHandler(handler = "DICOM-zip", allowCallsWithoutFiles = false)
 public class GradualDicomImporter extends ImporterHandlerA {
     public static final String SENDER_AE_TITLE_PARAM = "Sender-AE-Title";
     public static final String SENDER_ID_PARAM = "Sender-ID";
diff --git a/src/main/java/org/nrg/xnat/configuration/ReactorConfig.java b/src/main/java/org/nrg/xnat/configuration/ReactorConfig.java
old mode 100644
new mode 100755
index b5e5179ea4f0b948dd102bff597aa07211c0fd97..50fa720b87528104bbef90d131af4db18d59bf22
--- a/src/main/java/org/nrg/xnat/configuration/ReactorConfig.java
+++ b/src/main/java/org/nrg/xnat/configuration/ReactorConfig.java
@@ -11,7 +11,7 @@ import reactor.bus.EventBus;
  * The Class ReactorConfig.
  */
 @Configuration
-@ComponentScan({"org.nrg.xnat.event.listeners, org.nrg.xft.event.listeners"})
+@ComponentScan({"org.nrg.xnat.event.listeners, org.nrg.xft.event, org.nrg.xft.event.listeners"})
 public class ReactorConfig {
 
     /**
diff --git a/src/main/java/org/nrg/xnat/event/listeners/AutomatedScriptHandler.java b/src/main/java/org/nrg/xnat/event/listeners/AutomatedScriptHandler.java
old mode 100644
new mode 100755
index 2842b7c59aa212eaa3dc6ad0faa7522547d29510..2d9a5b9c63ef15d74ef8dffa8345969f080bdfc3
--- a/src/main/java/org/nrg/xnat/event/listeners/AutomatedScriptHandler.java
+++ b/src/main/java/org/nrg/xnat/event/listeners/AutomatedScriptHandler.java
@@ -48,6 +48,7 @@ public class AutomatedScriptHandler extends WorkflowStatusEventHandlerAbst imple
     
     /** The Constant logger. */
     private static final Logger logger = LoggerFactory.getLogger(AutomatedScriptHandler.class);
+	@Inject ScriptRunnerService _service;
 	
 	/**
 	 * Instantiates a new automated script handler.
@@ -113,8 +114,7 @@ public class AutomatedScriptHandler extends WorkflowStatusEventHandlerAbst imple
      * @return the scripts
      */
     private List<Script> getScripts(final String projectId, String event) {
-    	final ScriptRunnerService _service = XDAT.getContextService().getBean(ScriptRunnerService.class);
-    	
+
         final List<Script> scripts = Lists.newArrayList();
 
         //project level scripts
diff --git a/src/main/java/org/nrg/xnat/initialization/RootConfig.java b/src/main/java/org/nrg/xnat/initialization/RootConfig.java
index 3f7f48c6e9d88f37c4c6c42cb3093279c955f7fc..8c89712dbffb98ae0039990c9a0d8c8221e06532 100644
--- a/src/main/java/org/nrg/xnat/initialization/RootConfig.java
+++ b/src/main/java/org/nrg/xnat/initialization/RootConfig.java
@@ -7,6 +7,8 @@ import org.nrg.xdat.security.HistoricPasswordValidator;
 import org.nrg.xdat.security.PasswordValidatorChain;
 import org.nrg.xdat.security.RegExpValidator;
 import org.nrg.xnat.utils.XnatUserProvider;
+import org.nrg.xnat.restlet.XnatRestletExtensions;
+import org.nrg.xnat.restlet.actions.importer.ImporterHandlerPackages;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.ComponentScan;
@@ -15,6 +17,7 @@ import org.springframework.context.annotation.ImportResource;
 
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 
 @Configuration
@@ -77,6 +80,16 @@ public class RootConfig {
         return new XnatUserProvider(receivedFileUser);
     }
 
+    @Bean
+    public XnatRestletExtensions xnatRestletExtensions() {
+		return new XnatRestletExtensions(new HashSet<String>(Arrays.asList(new String[] {"org.nrg.xnat.restlet.extensions"})));
+    }
+
+    @Bean
+    public ImporterHandlerPackages importerHandlerPackages() {
+		return new ImporterHandlerPackages(new HashSet<String>(Arrays.asList(new String[] {"org.nrg.xnat.restlet.actions","org.nrg.xnat.archive"})));
+    }
+
     @Bean
     public SerializerRegistry serializerRegistry() {
         return new SerializerRegistry();
diff --git a/src/main/java/org/nrg/xnat/restlet/XNATApplication.java b/src/main/java/org/nrg/xnat/restlet/XNATApplication.java
old mode 100644
new mode 100755
index 5f2e44a6ad06475b24104c0dc064c5403fc0d758..a78986a6e74cc27e69c09aa9effc193367d4f23a
--- a/src/main/java/org/nrg/xnat/restlet/XNATApplication.java
+++ b/src/main/java/org/nrg/xnat/restlet/XNATApplication.java
@@ -361,17 +361,15 @@ public class XNATApplication extends Application {
     }
 
     /**
-     * This method walks the <b>org.nrg.xnat.restlet.extensions</b> package, as well as any packages defined in
-     * {@link XnatRestletExtensions} beans and attempts to find extensions for the
+     * This method walks the packages defined in * {@link XnatRestletExtensions} beans 
+     * (see org.nrg.xnat.configuration.RootConfig) and attempts to find extensions for the
      * set of available REST services.
-     *
      * @param router The URL router for the restlet servlet.
      * @return A list of classes that should be attached unprotected, i.e. publicly accessible.
      */
     @SuppressWarnings("unchecked")
     private List<Class<? extends Resource>> addExtensionRoutes(Router router) {
         Set<String> packages = new HashSet<>();
-        packages.add("org.nrg.xnat.restlet.extensions");
         final Map<String, XnatRestletExtensions> pkgLists = XDAT.getContextService().getBeansOfType(XnatRestletExtensions.class);
         for (XnatRestletExtensions pkgList : pkgLists.values()) {
             packages.addAll(pkgList);
diff --git a/src/main/java/org/nrg/xnat/restlet/XnatRestletExtensions.java b/src/main/java/org/nrg/xnat/restlet/XnatRestletExtensions.java
index df51c19e1cdbca3af34b86a917647f48977032bf..c583e55faf8870bb26c6f98e3d9bdbf3f041964c 100644
--- a/src/main/java/org/nrg/xnat/restlet/XnatRestletExtensions.java
+++ b/src/main/java/org/nrg/xnat/restlet/XnatRestletExtensions.java
@@ -1,13 +1,13 @@
 /*
  * org.nrg.xnat.restlet.XnatRestletExtensionList
  *
- * Copyright (c) 2014, Washington University School of Medicine
+ * Copyright (c) 2016, Washington University School of Medicine
  * All Rights Reserved
  *
  * XNAT is an open-source project of the Neuroinformatics Research Group.
  * Released under the Simplified BSD.
  *
- * Last modified 3/4/14 3:49 PM
+ * Last modified 1/19/16 3:49 PM
  */
 
 package org.nrg.xnat.restlet;
@@ -16,6 +16,10 @@ import java.util.HashSet;
 import java.util.Set;
 
 public class XnatRestletExtensions extends HashSet<String> {
+    public XnatRestletExtensions(Set<String> packages) {
+       super();
+       this.setPackages(packages);
+    }
     public void setPackages(Set<String> packages) {
         clear();
         addAll(packages);
diff --git a/src/main/java/org/nrg/xnat/restlet/actions/AutomationBasedImporter.java b/src/main/java/org/nrg/xnat/restlet/actions/AutomationBasedImporter.java
new file mode 100644
index 0000000000000000000000000000000000000000..90207e1afc30ebedb2bee1b9650b3b7d2506669e
--- /dev/null
+++ b/src/main/java/org/nrg/xnat/restlet/actions/AutomationBasedImporter.java
@@ -0,0 +1,524 @@
+package org.nrg.xnat.restlet.actions;
+
+/*
+ * org.nrg.xnat.restlet.actions.importer.handlers.AutomationBasedImporter
+ * XNAT http://www.xnat.org
+ * Copyright (c) 2014, Washington University School of Medicine
+ * All Rights Reserved
+ *
+ * Released under the Simplified BSD.
+ *
+ * Last modified 7/10/13 9:04 PM
+ */
+
+import java.io.File;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import java.util.concurrent.Callable;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang3.exception.ExceptionUtils;
+import org.apache.log4j.Logger;
+import org.nrg.action.ClientException;
+import org.nrg.action.ServerException;
+import org.nrg.automation.entities.Script;
+import org.nrg.automation.entities.ScriptOutput;
+import org.nrg.automation.entities.ScriptOutput.Status;
+import org.nrg.automation.services.ScriptRunnerService;
+import org.nrg.framework.constants.Scope;
+import org.nrg.framework.exceptions.NrgServiceException;
+import org.nrg.xdat.XDAT;
+import org.nrg.xdat.om.WrkWorkflowdata;
+import org.nrg.xdat.om.XnatExperimentdata;
+import org.nrg.xdat.om.XnatProjectdata;
+import org.nrg.xdat.om.XnatSubjectdata;
+import org.nrg.xdat.turbine.utils.AdminUtils;
+import org.nrg.xnat.restlet.actions.importer.ImporterHandler;
+import org.nrg.xnat.restlet.actions.importer.ImporterHandlerA;
+import org.nrg.xnat.restlet.files.utils.RestFileUtils;
+import org.nrg.xnat.restlet.util.FileWriterWrapperI;
+import org.nrg.xnat.services.messaging.automation.AutomatedScriptRequest;
+import org.nrg.xnat.turbine.utils.ArcSpecManager;
+import org.nrg.xnat.utils.WorkflowUtils;
+import org.json.JSONArray;
+import org.json.JSONException;
+//import net.sf.json.JSONObject;
+import org.json.JSONObject;
+import org.json.JSONTokener;
+
+import java.util.zip.ZipOutputStream;
+
+import org.nrg.xft.event.EventMetaI;
+import org.nrg.xft.event.EventUtils;
+import org.nrg.xft.event.EventUtils.CATEGORY;
+import org.nrg.xft.event.EventUtils.TYPE;
+import org.nrg.xft.event.persist.PersistentWorkflowI;
+import org.nrg.xft.event.persist.PersistentWorkflowUtils;
+import org.nrg.xft.event.persist.PersistentWorkflowUtils.EventRequirementAbsent;
+import org.nrg.xft.security.UserI;
+import org.nrg.xft.utils.zip.TarUtils;
+import org.nrg.xft.utils.zip.ZipI;
+import org.nrg.xft.utils.zip.ZipUtils;
+
+
+/**
+ * @author Mike Hodge <hodgem@mir.wustl.edu>
+ *
+ */
+@ImporterHandler(handler = "automation", allowCallsWithoutFiles = true, callPartialUriWrap = false)
+public class AutomationBasedImporter extends ImporterHandlerA implements Callable<List<String>> {
+ 	
+    private final ScriptRunnerService _service = XDAT.getContextService().getBean(ScriptRunnerService.class);
+
+	static final String[] ZIP_EXT={".zip",".jar",".rar",".ear",".gar",".xar"};
+
+	private static final String STATUS_COMPLETE = "Complete";
+	private static final String CACHE_CONSTANT = "_CACHE_";
+	private static final String CONFIG_TOOL = "resource_config";
+	private static final String CONFIG_SCRIPT_PATH = "script";
+	private static final String EMAIL_SUBJECT = "AutomationBasedImporter results";
+
+	static Logger logger = Logger.getLogger(AutomationBasedImporter.class);
+	
+	private final FileWriterWrapperI fw;
+	private final UserI user;
+	final Map<String,Object> params;
+	private final List<String> returnList = new ArrayList<String>();;
+	private String configuredResource; 
+	// Is this useful?  Do we want it to be configurable?
+	private boolean sendAdminEmail = false;
+	
+	/**
+	 * 
+	 * @param listenerControl
+	 * @param u
+	 * @param session
+	 * @param overwrite:   'append' means overwrite, but preserve un-modified content (don't delete anything)
+	 *                      'delete' means delete the pre-existing content.
+	 * @param additionalValues: should include project (subject and experiment are expected to be found in the archive)
+	 */
+	public AutomationBasedImporter(Object listenerControl, UserI u, FileWriterWrapperI fw, Map<String, Object> params) {
+		super(listenerControl, u, fw, params);
+		this.user=u;
+		this.fw=fw;
+		this.params=params;
+	}
+
+	@SuppressWarnings("deprecation")
+	@Override
+	public List<String> call() throws ClientException, ServerException {
+		try {
+			processUpload();
+			this.completed("Success");
+			return returnList;
+		} catch (ClientException e) {
+			logger.error("",e);
+			this.failed(e.getMessage());
+			throw e;
+		} catch (ServerException e) {
+			logger.error("",e);
+			this.failed(e.getMessage());
+			throw e;
+		} catch (Throwable e) {
+			logger.error("",e);
+			throw new ServerException(e.getMessage(),new Exception());
+		}
+	}
+
+	/*
+	@SuppressWarnings("deprecation")
+	private void clientFailed(final String fmsg) throws ClientException {
+		this.failed(fmsg);
+		throw new ClientException(fmsg,new Exception());
+	}
+	*/
+
+	private void processUpload() throws ClientException,ServerException {
+		
+		String cachePath = ArcSpecManager.GetInstance().getGlobalCachePath();
+		
+		final Object processParam = params.get("process");
+		final Object configuredResourceParam = params.get("configuredResource");
+		final Object buildPathParam = params.get("buildPath");
+		final Object sendemailParam = params.get("sendemail");
+		final boolean doProcess = processParam!=null && processParam.toString().equalsIgnoreCase("true");
+		configuredResource = (configuredResourceParam!=null) ? configuredResourceParam.toString() : CACHE_CONSTANT;
+		
+		// Uploads to configured resources are handled on the client side.  Only the workflow is generated by this resource
+		if (doProcess && !configuredResource.equalsIgnoreCase(CACHE_CONSTANT)) {
+			doAutomation();
+			return;
+		}
+		
+		// Multiple uploads are allowed to same space (processing will take place when process parameter=true).  Use specified build path when
+		// one is given, otherwise create new one
+		String specPath=null;
+		String buildPath = null;
+		if (buildPathParam!=null) {
+			// If buildpath parameter is specified and valid, use it
+			specPath=buildPathParam.toString();
+			if (specPath.indexOf(cachePath)>=0 && specPath.indexOf("user_uploads")>=0 &&
+					specPath.indexOf(File.separator + user.getID() + File.separator)>=0 && new File(specPath).isDirectory()) {
+				buildPath=specPath;
+			} else {
+				throw new ClientException("ERROR:  Specified build path is invalid or directory does not exist.");
+			}
+		} else  if (specPath==null && !(fw==null && doProcess)) {
+			final Date d = Calendar.getInstance().getTime();
+			final java.text.SimpleDateFormat formatter = new java.text.SimpleDateFormat ("yyyyMMdd_HHmmss");
+			final String uploadID = formatter.format(d);
+			// Save input files to cache space
+			buildPath = cachePath + "user_uploads/" + user.getID() + "/" + uploadID + "/";
+		} 
+		
+		File cacheLoc = null;
+		if (buildPath!=null) {
+			cacheLoc = new File(buildPath);
+			cacheLoc.mkdirs();
+		}
+		
+		// If uploading a file, process it.
+		if (fw!=null && cacheLoc!=null) {
+			processFile(cacheLoc, specPath);
+		} 
+		
+		// Conditionally process cache location files, otherwise return cache location
+		if (doProcess) {
+			doAutomation();
+			if (sendemailParam!=null && sendemailParam.toString().equalsIgnoreCase("true")) {
+				sendUserEmail(sendAdminEmail);
+			} else if (sendAdminEmail) {
+				sendAdminEmail();
+			}
+		} else if (buildPath!=null) {
+			returnList.add(buildPath);
+		}
+		
+	}
+	
+	private String returnListToHtmlString() {
+		final StringBuilder sb = new StringBuilder("<br/>");
+		for (final String s : returnList) {
+			sb.append(s).append("<br/>\t");
+		}
+		return sb.toString();
+	}
+	
+	private void sendUserEmail(boolean ccAdmin) {
+			AdminUtils.sendUserHTMLEmail(EMAIL_SUBJECT, returnListToHtmlString(), ccAdmin, new String[] { user.getEmail() });
+	}
+	
+	private void sendAdminEmail() {
+			AdminUtils.sendAdminEmail(user,EMAIL_SUBJECT, returnListToHtmlString());
+	}
+
+	private void processFile(final File cacheLoc,final  String specPath) throws ClientException {
+		final String fileName;
+		try {
+			fileName = URLDecoder.decode(fw.getName(),"UTF-8");
+		} catch (UnsupportedEncodingException e1) {
+			throw new ClientException("Could not decode file name.",e1);
+		}
+		final Object extractParam = params.get("extract");
+		if (extractParam!=null && extractParam.toString().equalsIgnoreCase("true") && isZipFileName(fileName)) {
+			final ZipI zipper = getZipper(fileName);
+			try {
+				zipper.extract(fw.getInputStream(),cacheLoc.getAbsolutePath());
+			} catch (Exception e) {
+				throw new ClientException("Archive file is corrupt or not a valid archive file type.");
+			}
+		} else {
+			final File cacheFile = new File(cacheLoc,fileName);
+			try {
+				fw.write(cacheFile);
+				// Uploading directories via linux (and likely Mac) will not fail due to "Everything is a file".  In such cases we wind 
+				// up with a "file" generated.  Check for these "files".  Remove them, and thrown an exception.
+				// Windows uploads of directories should fail before hitting this class.
+				removeAndThrowExceptionIfDirectory(cacheFile);
+			} catch (ClientException e) {
+				throw e;
+			} catch (Exception e) {
+				throw new ClientException("Could not write uploaded file.",e);
+			}
+		}
+	}
+	
+	private void removeAndThrowExceptionIfDirectory(File file) throws ClientException {
+		if (RestFileUtils.isFileRepresentationOfDirectory(file)) {
+			if (file.delete()) {
+				throw new ClientException("Upload of directories is not currently supported.  To upload a directory, please create a zip archive.");
+			};
+		}
+	}
+
+	private void doAutomation() throws ClientException, ServerException {
+		
+		returnList.add("<b>BEGIN PROCESSING UPLOADED FILES</b><br>");
+		
+		final Map<String,Object> passMap = Maps.newHashMap();
+		final Object eventHandlerParam = params.get("eventHandler");
+		final Object projectParam = params.get("project");
+		final Object subjectParam = params.get("subject");
+		final Object experimentParam = params.get("experiment");
+		final Object buildPathParam = params.get("buildPath");
+		final Object passedParametersParam = params.get("passedParameters");
+		String eventText = null; 
+		XnatProjectdata proj = null;
+		XnatSubjectdata subj = null;
+		XnatExperimentdata exp = null;
+		if (projectParam!=null && projectParam.toString().length()>0) {
+			proj = XnatProjectdata.getXnatProjectdatasById(projectParam, user, false);
+			if (proj != null) {
+				passMap.put("project", proj.getId());
+			}
+		}
+		if (subjectParam!=null && subjectParam.toString().length()>0) {
+			subj = XnatSubjectdata.getXnatSubjectdatasById(subjectParam.toString(), user, false);
+			if (subj == null) {		
+				subj = XnatSubjectdata.GetSubjectByProjectIdentifier(proj.getId(), subjectParam.toString(), user, false);
+			}
+			if (subj != null) {		
+				passMap.put("subject", subj.getId());
+			}
+		}
+		if (experimentParam!=null && experimentParam.toString().length()>0) {
+			exp = XnatExperimentdata.getXnatExperimentdatasById(experimentParam.toString(), user, false);
+			if (exp == null) {
+				exp = XnatExperimentdata.GetExptByProjectIdentifier(proj.getId(), experimentParam.toString(), user, false);
+			}
+			if (exp != null) {
+				passMap.put("experiment", exp.getId());
+			}
+		}
+		if (passedParametersParam!=null && passedParametersParam.toString().length()>0) {
+			try {
+				final String passedParametersJsonStr = URLDecoder.decode(passedParametersParam.toString(),"UTF-8");
+				final JSONObject json = new JSONObject(passedParametersJsonStr);
+				passMap.put("passedParameters", json);
+			} catch(UnsupportedEncodingException | JSONException e) {
+				// Do nothing for now.
+			}
+		}
+		if (!(configuredResource==null || configuredResource.equalsIgnoreCase(CACHE_CONSTANT))) {
+			eventText = getEventTextFromConfiguredResourceConfig(proj, subj, exp, configuredResource);
+			passMap.put("configuredResource", configuredResource);
+			
+		}
+		if (buildPathParam!=null) {
+			passMap.put("BuildPath", buildPathParam);
+		}
+		if (eventHandlerParam!=null && eventHandlerParam.toString().length()>0) {
+			eventText = eventHandlerParam.toString();
+		}	
+		if (eventText!=null) {
+			PersistentWorkflowI wrk = buildWorkflow(proj,passMap,eventText);
+			// Launch automation script for workflow handler
+			if (wrk instanceof WrkWorkflowdata) {
+				ScriptOutput scriptOut = launchScript((WrkWorkflowdata)wrk);
+				if (scriptOut!=null) {
+					returnList.add("<br><b>SCRIPT EXECUTION RESULTS</b>");
+					returnList.add("<br><b>FINAL STATUS:  " + scriptOut.getStatus() + "</b>");
+					if (scriptOut.getStatus().equals(Status.ERROR) && scriptOut.getResults()!=null && scriptOut.getResults().toString().length()>0) {
+						returnList.add("<br><b>SCRIPT RESULTS</b>");
+						returnList.add(scriptOut.getResults().toString().replace("\n", "<br>"));
+					}
+					if (scriptOut.getOutput()!=null && scriptOut.getOutput().length()>0) {
+						returnList.add("<br><b>SCRIPT STDOUT</b>");
+						returnList.add(scriptOut.getOutput().replace("\n", "<br>"));
+					}
+					if (scriptOut.getErrorOutput()!=null && scriptOut.getErrorOutput().length()>0) {
+						returnList.add("<br><b>SCRIPT STDERR/EXCEPTION</b>");
+						returnList.add(scriptOut.getErrorOutput().replace("\n", "<br>"));
+					}
+				}
+			}
+		}
+		
+		returnList.add("<br><b>FINISHED PROCESSING");
+		
+	}
+
+ 	private PersistentWorkflowI buildWorkflow(XnatProjectdata proj,Map<String, Object> passMap,String eventText) {
+		final PersistentWorkflowI wrk;
+		try {
+			returnList.add("Building workflow entry for configured resource / event handler - " + eventText);
+			wrk = PersistentWorkflowUtils.buildOpenWorkflow(user, params.get("xsiType").toString(), proj.getId(), proj.getId(),
+			EventUtils.newEventInstance(CATEGORY.DATA, TYPE.WEB_SERVICE, eventText, "Automation-based upload", null));
+			wrk.setStatus(STATUS_COMPLETE);
+			//wrk.setDetails(JSONObject.fromObject(passMap).toString());
+			wrk.setDetails((new JSONObject(passMap)).toString());
+			final EventMetaI em = wrk.buildEvent();
+			returnList.add("Saving workflow - " + eventText);
+			// Make workflow save request, requesting that the workflow not trigger an event.  We want to make
+			// the save request here.
+			PersistentWorkflowUtils.save(wrk, em, false, false);
+			return wrk;
+		} catch (EventRequirementAbsent e1) {
+			returnList.add("ERROR:  error generating workflow -" + e1.toString());
+			throw new NullPointerException(e1.getMessage());
+		} catch (Exception e) {
+			returnList.add("ERROR:  error generating workflow -" + e.toString());
+			logger.error("",e);
+		}
+		return null;
+	}
+
+    public ScriptOutput launchScript(WrkWorkflowdata wrk) {
+        if (StringUtils.equals(PersistentWorkflowUtils.COMPLETE, wrk.getStatus()) && !StringUtils.equals(wrk.getExternalid(), PersistentWorkflowUtils.ADMIN_EXTERNAL_ID)) {
+            //check to see if this has been handled before
+        	ScriptOutput scriptOut = null;
+            for (final Script script : getScripts(wrk.getExternalid(), wrk.getPipelineName())) {
+                try {
+                    //create a queued workflow to track this script
+                    final String action = "Executed script " + script.getScriptId();
+                    final String justification = wrk.getJustification();
+                    final String comment = "Executed script " + script.getScriptId() + " triggered by event " + wrk.getPipelineName();
+                    final PersistentWorkflowI scriptWrk = PersistentWorkflowUtils.buildOpenWorkflow(wrk.getUser(), wrk.getDataType(), wrk.getId(), wrk.getExternalid(),
+                    		EventUtils.newEventInstance(EventUtils.CATEGORY.DATA, EventUtils.TYPE.PROCESS, action, StringUtils.isNotBlank(justification) ? justification : "Automated execution: " + comment, comment));
+                    assert scriptWrk != null;
+                    scriptWrk.setStatus(PersistentWorkflowUtils.QUEUED);
+                    WorkflowUtils.save(scriptWrk, scriptWrk.buildEvent());
+                    final AutomatedScriptRequest request = new AutomatedScriptRequest(wrk.getWrkWorkflowdataId().toString(), wrk.getUser(), script.getScriptId(), 
+                    		wrk.getPipelineName(), scriptWrk.getWorkflowId().toString(), wrk.getDataType(), wrk.getId(), wrk.getExternalid());
+                    scriptOut = executeScriptRequest(request);
+                    return scriptOut;	
+                } catch (Exception e1) {
+                	if (scriptOut == null) {
+                		scriptOut = new ScriptOutput();
+                	}
+                	scriptOut.setErrorOutput((scriptOut.getErrorOutput()!=null) ? scriptOut.getErrorOutput() +
+                			ExceptionUtils.getStackTrace(e1) : ExceptionUtils.getStackTrace(e1));
+                	scriptOut.setOutput((scriptOut.getOutput()!=null) ? scriptOut.getOutput() : "");
+                    logger.error("", e1);
+                    return scriptOut;
+                }
+            }
+        }
+		return null;
+    }
+
+    private ScriptOutput executeScriptRequest(AutomatedScriptRequest request) throws Exception {
+    	
+        final PersistentWorkflowI workflow = WorkflowUtils.getUniqueWorkflow(request.getUser(), request.getScriptWorkflowId());
+        workflow.setStatus(PersistentWorkflowUtils.IN_PROGRESS);
+        WorkflowUtils.save(workflow, workflow.buildEvent());
+
+        final Map<String, Object> parameters = new HashMap<>();
+        parameters.put("user", request.getUser());
+        parameters.put("scriptId", request.getScriptId());
+        parameters.put("event", request.getEvent());
+        parameters.put("srcWorkflowId", request.getSrcWorkflowId());
+        parameters.put("scriptWorkflowId", request.getScriptWorkflowId());
+        parameters.put("dataType", request.getDataType());
+        parameters.put("dataId", request.getDataId());
+        parameters.put("externalId", request.getExternalId());
+        parameters.put("workflow", workflow);
+
+        ScriptOutput scriptOut = null;
+        try {
+            scriptOut = _service.runScript(_service.getScript(request.getScriptId()), null, parameters, false);
+            if (PersistentWorkflowUtils.IN_PROGRESS.equals(workflow.getStatus())) {
+                WorkflowUtils.complete(workflow, workflow.buildEvent());
+            }
+        } catch (NrgServiceException e) {
+            final String message = String.format("Failed running the script %s by user %s for event %s on data type %s instance %s from project %s",
+                    request.getScriptId(),
+                    request.getUser().getLogin(),
+                    request.getEvent(),
+                    request.getDataType(),
+                    request.getDataId(),
+                    request.getExternalId());
+            AdminUtils.sendAdminEmail("Script execution failure", message);
+            logger.error(message, e);
+            if (PersistentWorkflowUtils.IN_PROGRESS.equals(workflow.getStatus())) {
+                WorkflowUtils.fail(workflow, workflow.buildEvent());
+            }
+        }
+        return scriptOut;
+        
+	}
+
+	private List<Script> getScripts(final String projectId, final String event) {
+        final List<Script> scripts = Lists.newArrayList();
+
+        //project level scripts
+        if (StringUtils.isNotBlank(projectId)) {
+            final Script script = _service.getScript(Scope.Project, projectId, event);
+            if (script != null) {
+                scripts.add(script);
+            }
+        }
+
+        //site level scripts
+        final Script script = _service.getScript(Scope.Site, null, event);
+        if (script != null) {
+            scripts.add(script);
+        }
+
+        return scripts;
+    }
+ 	
+
+	// TODO:  Configured Resource configuration should be updated to store EVENT, rather than using this "Uploaded {label}" event. 
+	private String getEventTextFromConfiguredResourceConfig(XnatProjectdata proj, XnatSubjectdata subj, XnatExperimentdata exp, String configuredResource) {
+		final Scope scope = (exp!=null) ? Scope.Experiment : (subj!=null) ? Scope.Subject : Scope.Project; 
+		final String crConfig = XDAT.getConfigService().getConfigContents(CONFIG_TOOL,CONFIG_SCRIPT_PATH,scope,proj.getId());
+		if (crConfig!=null && crConfig.length()>0) {
+			try {
+				final JSONArray jsonArray = new JSONArray(new JSONTokener(crConfig));
+				for (int i=0;i<jsonArray.length();i++) {
+					final JSONObject jsonObj = jsonArray.getJSONObject(i);
+					if (jsonObj.getString("name").equals(configuredResource)) {
+						return "Uploaded " + jsonObj.getString("label");
+					}
+				}
+			} catch (JSONException e) {
+				logger.warn("WARNING:  Could not parse resource_config json results (PROJECT=" + proj.getId() + ",CONFIG_TOOL=" + CONFIG_TOOL + ",CONFIG_PATH=" + CONFIG_SCRIPT_PATH);
+			}
+		}
+		return null;
+	}
+
+	private boolean isZipFileName(final String fileName) {
+		for (final String ext : ZIP_EXT) {
+			if (fileName.toLowerCase().endsWith(ext)) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	private ZipI getZipper(final String fileName) {
+		
+		// Assume file name represents correct compression method
+		String file_extension = null;
+		if (fileName!=null && fileName.indexOf(".")!=-1) {
+			file_extension = fileName.substring(fileName.lastIndexOf(".")).toLowerCase();
+			if (Arrays.asList(ZIP_EXT).contains(file_extension)) {
+				return new ZipUtils();
+			} else if (file_extension.equalsIgnoreCase(".tar")) {
+				return new TarUtils();
+			} else if (file_extension.equalsIgnoreCase(".gz")) {
+				TarUtils zipper = new TarUtils();
+				zipper.setCompressionMethod(ZipOutputStream.DEFLATED);
+				return zipper;
+			}
+		}
+		// Assume zip-compression for unnamed inbody files
+		return new ZipUtils();
+		
+	}
+
+}
+
diff --git a/src/main/java/org/nrg/xnat/restlet/actions/SessionImporter.java b/src/main/java/org/nrg/xnat/restlet/actions/SessionImporter.java
index e1e55227345e3c62249086e2b4bd93c0fc136ec8..35c0039376c34186c4ab41dd0df4475d77b24841 100644
--- a/src/main/java/org/nrg/xnat/restlet/actions/SessionImporter.java
+++ b/src/main/java/org/nrg/xnat/restlet/actions/SessionImporter.java
@@ -38,6 +38,7 @@ import org.nrg.xnat.helpers.prearchive.SessionException;
 import org.nrg.xnat.helpers.uri.URIManager;
 import org.nrg.xnat.helpers.uri.UriParserUtils;
 import org.nrg.xnat.restlet.actions.PrearcImporterA.PrearcSession;
+import org.nrg.xnat.restlet.actions.importer.ImporterHandler;
 import org.nrg.xnat.restlet.actions.importer.ImporterHandlerA;
 import org.nrg.xnat.restlet.util.FileWriterWrapperI;
 import org.nrg.xnat.restlet.util.RequestUtil;
@@ -45,6 +46,7 @@ import org.nrg.xnat.turbine.utils.XNATSessionPopulater;
 import org.restlet.data.Status;
 import org.xml.sax.SAXException;
 
+@ImporterHandler(handler = "SI", allowCallsWithoutFiles = false)
 public class SessionImporter extends ImporterHandlerA implements Callable<List<String>> {
 
 	static Logger logger = Logger.getLogger(SessionImporter.class);
diff --git a/src/main/java/org/nrg/xnat/restlet/actions/XarImporter.java b/src/main/java/org/nrg/xnat/restlet/actions/XarImporter.java
index 2b2710bba3cd1ec2b5215a700d49a4ba835958bb..33cc7c759f0c7e948e836a012a3bdc71e067edbc 100644
--- a/src/main/java/org/nrg/xnat/restlet/actions/XarImporter.java
+++ b/src/main/java/org/nrg/xnat/restlet/actions/XarImporter.java
@@ -45,12 +45,14 @@ import org.nrg.xft.utils.SaveItemHelper;
 import org.nrg.xft.utils.zip.TarUtils;
 import org.nrg.xft.utils.zip.ZipI;
 import org.nrg.xft.utils.zip.ZipUtils;
+import org.nrg.xnat.restlet.actions.importer.ImporterHandler;
 import org.nrg.xnat.restlet.actions.importer.ImporterHandlerA;
 import org.nrg.xnat.restlet.util.FileWriterWrapperI;
 import org.nrg.xnat.turbine.utils.ArcSpecManager;
 import org.nrg.xnat.utils.WorkflowUtils;
 import org.xml.sax.SAXException;
 
+@ImporterHandler(handler = "XAR", allowCallsWithoutFiles = false)
 public class XarImporter extends ImporterHandlerA implements Callable<List<String>> {
 
 	static final String[] zipExtensions={".zip",".jar",".rar",".ear",".gar",".xar"};
diff --git a/src/main/java/org/nrg/xnat/restlet/actions/importer/ImporterHandler.java b/src/main/java/org/nrg/xnat/restlet/actions/importer/ImporterHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..38fdf24c68e0277df8f77766ddeef83a444e636c
--- /dev/null
+++ b/src/main/java/org/nrg/xnat/restlet/actions/importer/ImporterHandler.java
@@ -0,0 +1,27 @@
+/*
+ * org.nrg.xnat.restlet.actions.importer.ImporterHandler
+ * XNAT http://www.xnat.org
+ * Copyright (c) 2014, Washington University School of Medicine
+ * All Rights Reserved
+ *
+ * Released under the Simplified BSD.
+ *
+ * Last modified 7/10/13 9:04 PM
+ */
+package org.nrg.xnat.restlet.actions.importer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@Documented
+@Target(TYPE)
+@Retention(RUNTIME)
+public @interface ImporterHandler {
+    String handler();
+    boolean allowCallsWithoutFiles() default false;
+    boolean callPartialUriWrap() default true;
+}
diff --git a/src/main/java/org/nrg/xnat/restlet/actions/importer/ImporterHandlerA.java b/src/main/java/org/nrg/xnat/restlet/actions/importer/ImporterHandlerA.java
index da9f2b689e7934b2dd5e0d157536a116175ce06d..3c497ce31fb09aaee4356f8b92f9f88eed0c5fb7 100644
--- a/src/main/java/org/nrg/xnat/restlet/actions/importer/ImporterHandlerA.java
+++ b/src/main/java/org/nrg/xnat/restlet/actions/importer/ImporterHandlerA.java
@@ -10,33 +10,34 @@
  */
 package org.nrg.xnat.restlet.actions.importer;
 
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Callable;
-
 import org.apache.commons.lang.StringUtils;
 import org.apache.log4j.Logger;
 import org.nrg.action.ClientException;
 import org.nrg.action.ServerException;
 import org.nrg.dcm.DicomFileNamer;
 import org.nrg.framework.services.ContextService;
+import org.nrg.framework.utilities.Reflection;
 import org.nrg.status.StatusProducer;
 import org.nrg.xdat.XDAT;
 import org.nrg.xft.security.UserI;
 import org.nrg.xnat.DicomObjectIdentifier;
 import org.nrg.xnat.archive.DicomZipImporter;
 import org.nrg.xnat.archive.GradualDicomImporter;
-import org.nrg.xnat.restlet.actions.PrearcBlankSession;
-import org.nrg.xnat.restlet.actions.SessionImporter;
-import org.nrg.xnat.restlet.actions.XarImporter;
 import org.nrg.xnat.restlet.util.FileWriterWrapperI;
 import org.nrg.xdat.turbine.utils.PropertiesHelper;
 import org.springframework.beans.factory.NoSuchBeanDefinitionException;
 
-@SuppressWarnings({ "rawtypes", "unchecked" })
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+@SuppressWarnings({ "rawtypes", "unchecked", "deprecation" })
 public abstract class ImporterHandlerA  extends StatusProducer implements Callable<List<String>>{
 
 
@@ -64,20 +65,39 @@ public abstract class ImporterHandlerA  extends StatusProducer implements Callab
     private static final String CLASS_NAME = "className";
     private static final String[] PROP_OBJECT_FIELDS = new String[]{CLASS_NAME};
     static{
+    	//First, find importers by property file (if it exists)
         //EXAMPLE PROPERTIES FILE 
         //org.nrg.import.handler=NIFTI
-        //org.nrg.import.handler.impl.NIFTI.className=org.nrg.import.handler.CustomNiftiImporter
+        //org.nrg.import.handler.impl.NIFTI.className=org.nrg.import.handler.CustomNiftiImporter:w
         try {
             IMPORTERS.putAll((new PropertiesHelper<ImporterHandlerA>()).buildClassesFromProps(IMPORTER_PROPERTIES, PROP_OBJECT_IDENTIFIER, PROP_OBJECT_FIELDS, CLASS_NAME));
 
-            if(!IMPORTERS.containsKey(SESSION_IMPORTER))IMPORTERS.put(SESSION_IMPORTER, SessionImporter.class);
-            if(!IMPORTERS.containsKey(XAR_IMPORTER))IMPORTERS.put(XAR_IMPORTER, XarImporter.class);
-            if(!IMPORTERS.containsKey(GRADUAL_DICOM_IMPORTER))IMPORTERS.put(GRADUAL_DICOM_IMPORTER, GradualDicomImporter.class);
-            if(!IMPORTERS.containsKey(DICOM_ZIP_IMPORTER))IMPORTERS.put(DICOM_ZIP_IMPORTER, DicomZipImporter.class);
-	    if(!IMPORTERS.containsKey(BLANK_PREARCHIVE_ENTRY))IMPORTERS.put(BLANK_PREARCHIVE_ENTRY, PrearcBlankSession.class);
         } catch (Exception e) {
             logger.error("",e);
         }
+        //Second, find importers by annotation
+        final ImporterHandlerPackages packages = XDAT.getContextService().getBean("importerHandlerPackages",ImporterHandlerPackages.class);
+        for (final String pkg : packages) {
+			try {
+				final List<Class<?>> classesForPackage = Reflection.getClassesForPackage(pkg);
+				for (final Class<?> clazz : classesForPackage) {
+                    if (ImporterHandlerA.class.isAssignableFrom(clazz)) {
+                        if (!clazz.isAnnotationPresent(ImporterHandler.class)) {
+                       		continue;
+                       	}
+                       	ImporterHandler anno = clazz.getAnnotation(ImporterHandler.class);
+                        if (anno!=null && !IMPORTERS.containsKey(anno.handler())) {
+                        	if (logger.isDebugEnabled()) {
+                        		logger.debug("Found ImporterHandler: " + clazz.getName());
+                        	}
+                           	IMPORTERS.put(anno.handler(),(Class<? extends ImporterHandlerA>)clazz);
+                        }
+                    }
+				}
+			} catch (Exception e) {
+				throw new RuntimeException(e);
+			}
+        }
     }
 
     public static ImporterHandlerA buildImporter(String format,final Object uID, final UserI u, final FileWriterWrapperI fi, Map<String,Object> params) throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException, ImporterNotFoundException {
diff --git a/src/main/java/org/nrg/xnat/restlet/actions/importer/ImporterHandlerPackages.java b/src/main/java/org/nrg/xnat/restlet/actions/importer/ImporterHandlerPackages.java
new file mode 100644
index 0000000000000000000000000000000000000000..96b4317ba1de3e8689ff43ce5dbc5413409e7398
--- /dev/null
+++ b/src/main/java/org/nrg/xnat/restlet/actions/importer/ImporterHandlerPackages.java
@@ -0,0 +1,27 @@
+/*
+ * org.nrg.xnat.restlet.actions.importer.ImporterHandlerPackages
+ *
+ * Copyright (c) 2016, Washington University School of Medicine
+ * All Rights Reserved
+ *
+ * XNAT is an open-source project of the Neuroinformatics Research Group.
+ * Released under the Simplified BSD.
+ *
+ * Last modified 1/19/16 3:49 PM
+ */
+
+package org.nrg.xnat.restlet.actions.importer;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class ImporterHandlerPackages extends HashSet<String> {
+    public ImporterHandlerPackages(Set<String> packages) {
+    	super();
+        this.setPackages(packages);
+    }
+    public void setPackages(Set<String> packages) {
+        clear();
+        addAll(packages);
+    }
+}
diff --git a/src/main/java/org/nrg/xnat/restlet/files/utils/RestFileUtils.java b/src/main/java/org/nrg/xnat/restlet/files/utils/RestFileUtils.java
index 735aa1048442f355a5d4ee534da68c3072779d01..2f9a826824fcdfee5b521554820a20b001290a29 100644
--- a/src/main/java/org/nrg/xnat/restlet/files/utils/RestFileUtils.java
+++ b/src/main/java/org/nrg/xnat/restlet/files/utils/RestFileUtils.java
@@ -13,6 +13,10 @@ package org.nrg.xnat.restlet.files.utils;
 import org.apache.commons.lang.StringUtils;
 import org.nrg.xdat.om.*;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.util.Hashtable;
 import java.util.List;
 import java.util.Map;
@@ -155,5 +159,34 @@ public class RestFileUtils {
 		
 		return session_ids;
 	}
+	
+	// Uploading directories via linux (and likely Mac) will not fail due to "Everything is a file".  This is an initial
+	// implementation of a check of files to see if they might be uploaded "directories".  These file representations directories
+	// seem to be of a specific size and basically full of zero bytes.  It's possible this check could/should be improved over time.
+	public static boolean isFileRepresentationOfDirectory(File fl) {
+		final long len = fl.length();
+		// Is this the best check?
+		if (len < 1024 || (len < (128*1024) && len%1024 == 0)) {
+			try {
+				final FileInputStream fis = new FileInputStream(fl);
+				byte[] b = new byte[1024];
+				while (fis.read(b)!=-1) {
+					for (int i=0; i<b.length; i++) {
+						if (b[i] != 0) {
+							fis.close();
+							return false;
+						}
+					}
+				}
+				fis.close();
+				return true;
+			} catch (FileNotFoundException e) {
+				return false;
+			} catch (IOException e) {
+				return false;
+			}
+		}
+		return false;
+	}
 
 }
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 53d71800437776078d5605dc17da5891d8f311f7..14b078f347275d05ac8c8c49e0468882f04e484e 100644
--- a/src/main/java/org/nrg/xnat/restlet/resources/EventResource.java
+++ b/src/main/java/org/nrg/xnat/restlet/resources/EventResource.java
@@ -1,102 +1,296 @@
-package org.nrg.xnat.restlet.resources;
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import org.apache.commons.lang.StringUtils;
-import org.nrg.xft.XFTTable;
-import org.nrg.xft.exception.DBPoolException;
-import org.restlet.Context;
-import org.restlet.data.*;
-import org.restlet.resource.Representation;
-import org.restlet.resource.ResourceException;
-import org.restlet.resource.StringRepresentation;
-import org.restlet.resource.Variant;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.sql.SQLException;
-import java.util.HashMap;
-import java.util.Map;
-
-public class EventResource extends AutomationResource {
-
-    public EventResource(Context context, Request request, Response response) throws ResourceException {
-        super(context, request, response);
-        getVariants().add(new Variant(MediaType.APPLICATION_JSON));
-        getVariants().add(new Variant(MediaType.TEXT_HTML));
-        getVariants().add(new Variant(MediaType.TEXT_XML));
-        getVariants().add(new Variant(MediaType.TEXT_PLAIN));
-
-        _event = (String) getRequest().getAttributes().get(EVENT);
-
-        if (!request.getMethod().equals(Method.GET)) {
-            response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "XNAT events are currently read-only: you can only do GET requests.");
-            throw new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST, "XNAT events are currently read-only: you can only do GET requests.");
-        }
-
-        if (_log.isDebugEnabled()) {
-            if (StringUtils.isNotBlank(_event)) {
-                _log.debug("Servicing event request for event ID " + _event + " for user " + user.getLogin());
-            } else {
-                _log.debug("Retrieving available events for user " + user.getLogin());
-            }
-        }
-    }
-
-    @Override
-    protected String getResourceType() {
-        return "Event";
-    }
-
-    @Override
-    protected String getResourceId() {
-        return _event;
-    }
-
-    @Override
-    public Representation represent(Variant variant) throws ResourceException {
-        final MediaType mediaType = overrideVariant(variant);
-
-        try {
-            if (StringUtils.isNotBlank(_event)) {
-                // They're requesting a specific event, so return that to them.
-                final XFTTable table = getEventsTable();
-                final Map<Object, Object> eventMap = table.convertToHashtable("event_id", "event_name");
-                if (!eventMap.containsKey(_event)) {
-                    throw new ResourceException(Status.CLIENT_ERROR_NOT_FOUND, "No event of ID " + _event + " was found.");
-                } else {
-                    final String label = (String) eventMap.get(_event);
-                    final Map<String, String> event = new HashMap<>();
-                    event.put("event_id", _event);
-                    event.put("event_label", label);
-                    return new StringRepresentation(MAPPER.writeValueAsString(event), mediaType);
-                }
-            } else {
-                // They're asking for list of existing script events, so give them that.
-                final XFTTable table = getEventsTable();
-                return representTable(table, mediaType, null);
-            }
-        } catch (JsonProcessingException e) {
-            throw new ResourceException(Status.SERVER_ERROR_INTERNAL, "An error occurred marshalling the event data to JSON", e);
-        } catch (SQLException | DBPoolException e) {
-            throw new ResourceException(Status.SERVER_ERROR_INTERNAL, "An error occurred accessing the database.", e);
-        }
-    }
-
-    // TODO: Convert this to use a separate events registry service.
-    private XFTTable getEventsTable() throws SQLException, DBPoolException {
-        final XFTTable table = XFTTable.Execute("SELECT DISTINCT CASE pipeline_name\n" +
-                "            WHEN 'Transfer'::text THEN 'Archive'::text\n" +
-                "                    ELSE\n" +
-                "                                CASE xs_lastposition('/'::text, pipeline_name::text) WHEN 0 THEN pipeline_name ELSE\n" +
-                "                                substring(substring(pipeline_name::text, xs_lastposition('/'::text, pipeline_name::text) + 1), 1, xs_lastposition('.'::text, substring(pipeline_name::text, xs_lastposition('/'::text, pipeline_name::text) + 1)) - 1)\n" +
-                "        END END AS event_label, pipeline_name AS event_id FROM wrk_workflowData  WHERE externalid !='ADMIN' AND externalid !='' AND externalid IS NOT NULL;\n", user.getDBName(), userName);
-
-        table.sort("event_label", "ASC");
-        return table;
-    }
-
-    private static final Logger _log = LoggerFactory.getLogger(EventResource.class);
-
-    private static final String EVENT = "EVENT";
-    private final String _event;
-}
+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.hibernate.HibernateException;
+import org.nrg.action.ClientException;
+import org.nrg.action.ServerException;
+import org.nrg.automation.entities.Event;
+import org.nrg.automation.entities.ScriptTrigger;
+import org.nrg.automation.services.EventService;
+import org.nrg.automation.services.ScriptTriggerService;
+import org.nrg.xdat.security.helpers.Roles;
+import org.nrg.xdat.XDAT;
+import org.nrg.xft.XFTTable;
+import org.nrg.xft.exception.DBPoolException;
+import org.restlet.Context;
+import org.restlet.data.*;
+import org.restlet.resource.Representation;
+import org.restlet.resource.ResourceException;
+import org.restlet.resource.StringRepresentation;
+import org.restlet.resource.Variant;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.nio.charset.Charset;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+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";
+
+    public EventResource(Context context, Request request, Response response) throws ResourceException {
+        super(context, request, response);
+
+        getVariants().add(new Variant(MediaType.APPLICATION_JSON));
+        getVariants().add(new Variant(MediaType.TEXT_HTML));
+        getVariants().add(new Variant(MediaType.TEXT_XML));
+        getVariants().add(new Variant(MediaType.TEXT_PLAIN));
+
+        _service = XDAT.getContextService().getBean(EventService.class);
+        _scriptTriggerService = XDAT.getContextService().getBean(ScriptTriggerService.class);
+        _cascade = isQueryVariableTrue("cascade");
+
+        final String eventId;
+        try {
+            if (getRequest().getAttributes().containsKey(EVENT_ID)) {
+                eventId = URLDecoder.decode((String) getRequest().getAttributes().get(EVENT_ID), "UTF-8");
+            } else {
+                eventId = null;
+            }
+        } catch (UnsupportedEncodingException exception) {
+            // This is the stupidest exception ever. From the docs:
+            //
+            // The supplied encoding is used to determine what characters are represented by any consecutive sequences
+            // of the form "%xy". Note: The World Wide Web Consortium Recommendation states that UTF-8 should be used.
+            // Not doing so may introduce incompatibilities.
+            //
+            // So in other words you have to specify an encoding then handle the exception if you specify an unsupported
+            // encoding, except that the only encoding you should use is "UTF-8" and you should specify that every time.
+            throw new ResourceException(Status.SERVER_ERROR_INTERNAL, "Something stupid happened. Sorry about that.", exception);
+        }
+
+        getParameters();
+
+        if (!_properties.containsKey(PROPERTY_EVENT_ID) && StringUtils.isNotBlank(eventId)) {
+            _properties.setProperty(PROPERTY_EVENT_ID, eventId);
+        }
+
+        final Method method = request.getMethod();
+        if (method.equals(Method.PUT) || method.equals(Method.DELETE)) {
+            if (!Roles.isSiteAdmin(user)) {
+                getResponse().setStatus(Status.CLIENT_ERROR_FORBIDDEN, "Only site administrators can create, update, or delete an event.");
+            }
+            if (!_properties.containsKey(PROPERTY_EVENT_ID)) {
+                getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "You must specify the event ID parameter on the REST URI when creating, updating, or deleting an event.");
+            }
+            if (method == Method.PUT && !_properties.containsKey(PROPERTY_EVENT_LABEL)) {
+                getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "Unable to find event label: no data sent?");
+            }
+            if (StringUtils.isNotBlank(eventId) && !StringUtils.equals(_properties.getProperty(PROPERTY_EVENT_ID), eventId)) {
+                getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "The event ID found in the form data or querystring parameters doesn't match the REST URI. Event IDs are immutable and can not be changed.");
+            }
+        }
+
+        final Status status = getResponse().getStatus();
+        if (status.getCode() < Status.CLIENT_ERROR_BAD_REQUEST.getCode()) {
+            _log.info("Got status " + status + ", won't process further: " + status.getDescription());
+        } else if (_log.isDebugEnabled()) {
+            if (StringUtils.isNotBlank(getResourceId())) {
+                _log.debug("Servicing event request for event ID " + getResourceId() + " for user " + user.getLogin());
+            } else {
+                _log.debug("Retrieving available events for user " + user.getLogin());
+            }
+        }
+    }
+
+    @Override
+    public boolean allowPut() {
+        return true;
+    }
+
+    @Override
+    public boolean allowDelete() {
+        return true;
+    }
+
+    @Override
+    protected String getResourceType() {
+        return "Event";
+    }
+
+    @Override
+    protected String getResourceId() {
+        if (_properties.containsKey(PROPERTY_EVENT_ID)) {
+            return _properties.getProperty(PROPERTY_EVENT_ID);
+        }
+        return null;
+    }
+
+    @Override
+    public Representation represent(Variant variant) throws ResourceException {
+        final MediaType mediaType = overrideVariant(variant);
+
+        try {
+            if (StringUtils.isNotBlank(getResourceId())) {
+                // They're requesting a specific event, so return that to them.
+                final XFTTable table = getEventsTable();
+                final Map<Object, Object> eventMap = table.convertToHashtable(PROPERTY_EVENT_ID, PROPERTY_EVENT_LABEL);
+                if (!eventMap.containsKey(getResourceId())) {
+                    throw new ResourceException(Status.CLIENT_ERROR_NOT_FOUND, "No event of ID " + getResourceId() + " was found.");
+                } else {
+                    final String label = (String) eventMap.get(getResourceId());
+                    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);
+                }
+            } else {
+                // They're asking for list of existing script events, so give them that.
+                final XFTTable table = getEventsTable();
+                return representTable(table, mediaType, null);
+            }
+        } catch (IOException e) {
+            throw new ResourceException(Status.SERVER_ERROR_INTERNAL, "An error occurred marshalling the event data to JSON", e);
+        } catch (SQLException | DBPoolException e) {
+            throw new ResourceException(Status.SERVER_ERROR_INTERNAL, "An error occurred accessing the database.", e);
+        }
+    }
+
+    @Override
+    public void handlePut() {
+        try {
+            if (_log.isDebugEnabled()) {
+                _log.debug("Preparing to PUT event: " + getResourceId());
+            }
+            putEvent();
+        } catch (ClientException e) {
+            _log.error("Client error occurred trying to store an event: " + getResourceId(), e);
+            getResponse().setStatus(e.getStatus(), e.getMessage());
+        } catch (ServerException e) {
+            _log.error("Server error occurred trying to store an event: " + getResourceId(), e);
+            getResponse().setStatus(e.getStatus(), e.getMessage());
+        }
+    }
+
+    @Override
+    public void handleDelete() {
+        try {
+            if (_log.isDebugEnabled()) {
+                _log.debug("Preparing to DELETE event: " + getResourceId());
+            }
+            deleteEvent();
+        } catch (ClientException e) {
+            _log.error("Client error occurred trying to delete an event: " + getResourceId(), e);
+            getResponse().setStatus(e.getStatus(), e.getMessage());
+        } catch (ServerException e) {
+            _log.error("Server error occurred trying to delete an event: " + getResourceId(), e);
+            getResponse().setStatus(e.getStatus(), e.getMessage());
+        }
+    }
+
+    private void getParameters() throws ResourceException {
+        final Map<String, String> queryParameters = getQueryVariableMap();
+        if (queryParameters.size() > 0) {
+            _properties.putAll(queryParameters);
+        }
+        final Method method = getRequest().getMethod();
+        if (method != Method.GET) {
+            final Representation entity = getRequest().getEntity();
+            if (entity.getSize() > 0) {
+                final MediaType mediaType = entity.getMediaType();
+                if (!mediaType.equals(MediaType.APPLICATION_WWW_FORM) && !mediaType.equals(MediaType.APPLICATION_JSON)) {
+                    throw new ResourceException(Status.CLIENT_ERROR_UNSUPPORTED_MEDIA_TYPE, "This function currently only supports " + MediaType.APPLICATION_WWW_FORM + " and " + MediaType.APPLICATION_JSON);
+                }
+                if (mediaType.equals(MediaType.APPLICATION_WWW_FORM)) {
+                    try {
+                        final List<NameValuePair> formMap = URLEncodedUtils.parse(entity.getText(), DEFAULT_CHARSET);
+                        for (final NameValuePair entry : formMap) {
+                            _properties.setProperty(entry.getName(), entry.getValue());
+                        }
+                    } catch (IOException e) {
+                        throw new ResourceException(Status.SERVER_ERROR_INTERNAL, "An error occurred trying to read the submitted form body.", e);
+                    }
+                } else {
+                    try {
+                        final String text = entity.getText();
+                        _properties.putAll(MAPPER.readValue(text, Properties.class));
+                    } catch (IOException e) {
+                        throw new ResourceException(Status.SERVER_ERROR_INTERNAL, "An error occurred processing the script properties", e);
+                    }
+                }
+            }
+        }
+    }
+
+    private void putEvent() throws ClientException, ServerException {
+        final String eventId = _properties.getProperty(PROPERTY_EVENT_ID);
+        final String eventLabel = _properties.getProperty(PROPERTY_EVENT_LABEL);
+
+        final Event event;
+        final boolean created;
+        if (_service.hasEvent(eventId)) {
+            event = _service.getByEventId(eventId);
+            boolean isDirty = false;
+            if (!StringUtils.equals(eventLabel, event.getEventLabel())) {
+                isDirty = true;
+                event.setEventLabel(eventLabel);
+            }
+            if (isDirty) {
+                _service.update(event);
+            }
+            created = false;
+        } else {
+            event = _service.create(getResourceId(), eventLabel);
+            created = true;
+        }
+
+        recordAutomationEvent(event.getEventId(), SITE_SCOPE, created ? "Create" : "Update", Event.class);
+    }
+
+    private void deleteEvent() throws ClientException, ServerException {
+        final String eventId = getResourceId();
+        if (!_service.hasEvent(eventId)) {
+            throw new ClientException(Status.CLIENT_ERROR_NOT_FOUND, "Couldn't find an event with the event ID " + eventId);
+        }
+
+        final List<ScriptTrigger> triggers = _scriptTriggerService.getByEvent(eventId);
+        if (triggers != null && triggers.size() > 0) {
+            if (!_cascade) {
+                throw new ClientException(Status.CLIENT_ERROR_FORBIDDEN, "There are " + triggers.size() + " event handlers that reference the indicated event ID " + eventId + ". Please delete these triggers directly or call this method with the \"cascade=true\" query parameter: " + getRequest().getResourceRef().toString() + "?cascade=true");
+            }
+            for (final ScriptTrigger trigger : triggers) {
+                _log.info("Deleting script trigger: " + trigger.getTriggerId());
+                _scriptTriggerService.delete(trigger);
+            }
+        }
+        try {
+            _service.delete(_service.getByEventId(eventId));
+            recordAutomationEvent(eventId, SITE_SCOPE, "Delete", Event.class);
+        } catch (HibernateException e) {
+            throw new ServerException(Status.SERVER_ERROR_INTERNAL, "An error occurred trying to delete the event " + eventId);
+        }
+    }
+
+    private XFTTable getEventsTable() throws SQLException, DBPoolException {
+        final EventService eventService = XDAT.getContextService().getBean(EventService.class);
+        final List<Event> events = eventService.getAll();
+        final XFTTable table = new XFTTable();
+        table.initTable(new String[] { PROPERTY_EVENT_LABEL, PROPERTY_EVENT_ID });
+
+        for (final Event event : events) {
+            table.insertRow(new String[] { event.getEventLabel(), event.getEventId() });
+        }
+
+        table.sort(PROPERTY_EVENT_LABEL, "ASC");
+        return table;
+    }
+
+    private static final Logger _log = LoggerFactory.getLogger(EventResource.class);
+    private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
+    private static final String EVENT_ID = "EVENT_ID";
+
+    private final EventService _service;
+    private final ScriptTriggerService _scriptTriggerService;
+    private final Properties _properties = new Properties();
+    private final boolean _cascade;
+}
diff --git a/src/main/java/org/nrg/xnat/restlet/services/Importer.java b/src/main/java/org/nrg/xnat/restlet/services/Importer.java
index d625f19e7905371c36eb0b4be5765a88b65ce135..e0a67f595b0bdfcbd9029e94aa595b47472f5302 100644
--- a/src/main/java/org/nrg/xnat/restlet/services/Importer.java
+++ b/src/main/java/org/nrg/xnat/restlet/services/Importer.java
@@ -15,7 +15,9 @@ import org.apache.commons.lang.StringUtils;
 import org.nrg.action.ClientException;
 import org.nrg.action.ServerException;
 import org.nrg.framework.constants.PrearchiveCode;
+import org.nrg.framework.utilities.Reflection;
 import org.nrg.status.StatusList;
+import org.nrg.xdat.XDAT;
 import org.nrg.xdat.om.XnatProjectdata;
 import org.nrg.xdat.turbine.utils.TurbineUtils;
 import org.nrg.xnat.helpers.file.StoredFile;
@@ -25,6 +27,8 @@ import org.nrg.xnat.helpers.transactions.PersistentStatusQueueManagerI;
 import org.nrg.xnat.helpers.uri.URIManager;
 import org.nrg.xnat.helpers.uri.UriParserUtils;
 import org.nrg.xnat.helpers.uri.UriParserUtils.UriParser;
+import org.nrg.xnat.restlet.actions.importer.ImporterHandler;
+import org.nrg.xnat.restlet.actions.importer.ImporterHandlerPackages;
 import org.nrg.xnat.restlet.actions.importer.ImporterHandlerA;
 import org.nrg.xnat.restlet.actions.importer.ImporterNotFoundException;
 import org.nrg.xnat.restlet.resources.SecureResource;
@@ -41,13 +45,18 @@ import org.restlet.util.Template;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.collect.Lists;
+
 import java.io.File;
+import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
 import java.net.MalformedURLException;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.Hashtable;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 public class Importer extends SecureResource {
 	private static final String CRLF = "\r\n";
@@ -64,6 +73,33 @@ public class Importer extends SecureResource {
 		this.getVariants().add(new Variant(MediaType.TEXT_XML));
 		this.getVariants().add(new Variant(MediaType.TEXT_PLAIN));
 	}
+	
+	private static final List<String> HANDLERS_ALLOWING_CALLS_WITHOUT_FILES = Lists.newArrayList(); 
+	private static final List<String> HANDLERS_PREFERRING_PARTIAL_URI_WRAP = Lists.newArrayList(); 
+	static {
+        final ImporterHandlerPackages packages = XDAT.getContextService().getBean("importerHandlerPackages",ImporterHandlerPackages.class);
+        for (final String pkg : packages) {
+			try {
+				final List<Class<?>> classesForPackage = Reflection.getClassesForPackage(pkg);
+				for (final Class<?> clazz : classesForPackage) {
+                    if (ImporterHandlerA.class.isAssignableFrom(clazz)) {
+                        if (!clazz.isAnnotationPresent(ImporterHandler.class)) {
+                       		continue;
+                       	}
+                       	ImporterHandler anno = clazz.getAnnotation(ImporterHandler.class);
+                        if (anno!=null && anno.allowCallsWithoutFiles()) {
+                        	HANDLERS_ALLOWING_CALLS_WITHOUT_FILES.add(anno.handler());
+                        }
+                        if (anno!=null && anno.callPartialUriWrap()) {
+                        	HANDLERS_PREFERRING_PARTIAL_URI_WRAP.add(anno.handler());
+                        }
+                    }
+				}
+			} catch (Exception e) {
+				throw new RuntimeException(e);
+			}
+        }
+	}
 
 	@Override
 	public boolean allowGet(){
@@ -151,54 +187,51 @@ public class Importer extends SecureResource {
 				}
 			}
 			
-			if(fw.size()==0 && handler != null && !handler.equals(ImporterHandlerA.BLANK_PREARCHIVE_ENTRY))
-			{
+			if(fw.size()==0 && handler != null && !HANDLERS_ALLOWING_CALLS_WITHOUT_FILES.contains(handler)) {
+				
 				this.getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "Unable to identify upload format.");
 				return;
-			}
-			else if (handler != null && fw.size() == 0) {
-				if (!handler.equals(ImporterHandlerA.BLANK_PREARCHIVE_ENTRY)) {
-					throw new ClientException(Status.CLIENT_ERROR_BAD_REQUEST, "For a POST request with no file, the \"" + ImporterHandlerA.IMPORT_HANDLER_ATTR + "\" parameter can only be \"" + ImporterHandlerA.BLANK_PREARCHIVE_ENTRY + "\".", new IllegalArgumentException());
+				
+			} else if (handler != null && fw.size() == 0) {
+				
+				try {				
+					importer = ImporterHandlerA.buildImporter(handler, 
+															  listenerControl, 
+															  user, 
+															  null, // FileWriterWrapperI is null because no files should have been uploaded. 
+															  params);
 				}
-				else {
-					try {				
-						importer = ImporterHandlerA.buildImporter(handler, 
-																  listenerControl, 
-																  user, 
-																  null, // FileWriterWrapperI is null because no files should have been uploaded. 
-																  params);
-					}
-					catch (Exception e) {
-						logger.error("",e);
-						throw new ServerException(e.getMessage(),e);
+				catch (Exception e) {
+					logger.error("",e);
+					throw new ServerException(e.getMessage(),e);
+				}
+				
+				if(httpSessionListener){
+					if(StringUtils.isEmpty(listenerControl)){
+						getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST,"'" + XNATRestConstants.TRANSACTION_RECORD_ID+ "' is required when requesting '" + HTTP_SESSION_LISTENER + "'.");
+						return;
 					}
-					
-					if(httpSessionListener){
-						if(StringUtils.isEmpty(listenerControl)){
-							getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST,"'" + XNATRestConstants.TRANSACTION_RECORD_ID+ "' is required when requesting '" + HTTP_SESSION_LISTENER + "'.");
-							return;
-						}
-						final StatusList sq = new StatusList();
-						importer.addStatusListener(sq);
+					final StatusList sq = new StatusList();
+					importer.addStatusListener(sq);
 
-						storeStatusList(listenerControl, sq);
-					}
+					storeStatusList(listenerControl, sq);
+				}
 
-					response= importer.call();
-								
-					if(entity!=null && APPLICATION_XMIRC.equals(entity.getMediaType())){
-						returnString("OK", Status.SUCCESS_OK);
-						return;
-					}
-					
-					returnDefaultRepresentation();
+				response= importer.call();
+							
+				if(entity!=null && APPLICATION_XMIRC.equals(entity.getMediaType())){
+					returnString("OK", Status.SUCCESS_OK);
 					return;
 				}
-			}
-
-			if(fw.size()>1){
+				
+				returnDefaultRepresentation();
+				return;
+				
+			} else if (fw.size()>1){
+				
 				this.getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "Importer is limited to one uploaded resource at a time.");
 				return;
+				
 			}
 
 			if(handler==null && entity!=null){
@@ -330,16 +363,17 @@ public class Importer extends SecureResource {
 		@Override
 		public Representation represent(Variant variant) throws ResourceException {
 			final MediaType mt=overrideVariant(variant);
+			final boolean wrapURI = (handler==null || HANDLERS_PREFERRING_PARTIAL_URI_WRAP.contains(handler));
 			if(mt.equals(MediaType.TEXT_HTML)){
 				return buildHTMLresponse(response);
 			}else if(mt.equals(MediaType.TEXT_PLAIN)){
-				if(response!=null&& response.size()==1){
-					return new StringRepresentation(wrapPartialDataURI(response.get(0)), MediaType.TEXT_PLAIN);
+				if(response!=null && response.size()==1){
+					return new StringRepresentation((wrapURI) ? wrapPartialDataURI(response.get(0)) : response.get(0), MediaType.TEXT_PLAIN);
 				}else{
-					return new StringRepresentation(convertListURItoString(response), MediaType.TEXT_PLAIN);
+					return new StringRepresentation(convertListToString(response,wrapURI), MediaType.TEXT_PLAIN);
 				}
 			}else{
-				return new StringRepresentation(convertListURItoString(response), MediaType.TEXT_URI_LIST);
+				return new StringRepresentation(convertListToString(response, wrapURI), MediaType.TEXT_URI_LIST);
 			}
 		}
 
@@ -437,10 +471,10 @@ public class Importer extends SecureResource {
 		}
 	}
 
-	public String convertListURItoString(final List<String> response){
-		StringBuffer sb = new StringBuffer();
+	public String convertListToString(final List<String> response, boolean wrapPartialDataURI){
+		final StringBuffer sb = new StringBuffer();
 		for(final String s:response){
-			sb.append(wrapPartialDataURI(s)).append(CRLF);
+			sb.append((wrapPartialDataURI) ? wrapPartialDataURI(s) : s).append(CRLF);
 		}
 
 		return sb.toString();
diff --git a/src/main/java/org/nrg/xnat/services/messaging/automation/AutomatedScriptRequestListener.java b/src/main/java/org/nrg/xnat/services/messaging/automation/AutomatedScriptRequestListener.java
index 9bdc6a36e3b632e384c31009bc23d1945bb42200..7b87c90eeaa20e506b116a3e616597185878b77a 100644
--- a/src/main/java/org/nrg/xnat/services/messaging/automation/AutomatedScriptRequestListener.java
+++ b/src/main/java/org/nrg/xnat/services/messaging/automation/AutomatedScriptRequestListener.java
@@ -1,5 +1,6 @@
 package org.nrg.xnat.services.messaging.automation;
 
+import org.nrg.automation.entities.ScriptOutput;
 import org.nrg.automation.services.ScriptRunnerService;
 import org.nrg.framework.exceptions.NrgServiceException;
 import org.nrg.xdat.turbine.utils.AdminUtils;
diff --git a/src/main/java/org/nrg/xnat/turbine/modules/actions/AddProject.java b/src/main/java/org/nrg/xnat/turbine/modules/actions/AddProject.java
old mode 100644
new mode 100755
index 1faf9fe7feb7b7c8d442b3be9e8c739688b61918..37352e2ef36f2bfec373caa55f3e5f6f42d8d0a6
--- a/src/main/java/org/nrg/xnat/turbine/modules/actions/AddProject.java
+++ b/src/main/java/org/nrg/xnat/turbine/modules/actions/AddProject.java
@@ -28,7 +28,7 @@ import org.nrg.xft.XFTItem;
 import org.nrg.xft.db.PoolDBUtils;
 import org.nrg.xft.event.EventMetaI;
 import org.nrg.xft.event.EventUtils;
-import org.nrg.xft.event.ReactorEventUtils;
+import org.nrg.xft.event.XftEventService;
 import org.nrg.xft.event.XftItemEvent;
 import org.nrg.xft.event.persist.PersistentWorkflowI;
 import org.nrg.xft.event.persist.PersistentWorkflowUtils;
@@ -164,7 +164,7 @@ public class AddProject extends SecureAction {
                     WorkflowUtils.complete(wrk, c);
 
                     UserHelper.setUserHelper(data.getRequest(),user);
-                    ReactorEventUtils.triggerEvent(new XftItemEvent(XnatProjectdata.SCHEMA_ELEMENT_NAME, postSave.getId(), XftItemEvent.UPDATE));
+                    XftEventService.getService().triggerEvent(new XftItemEvent(XnatProjectdata.SCHEMA_ELEMENT_NAME, postSave.getId(), XftItemEvent.UPDATE));
                 } catch (Exception e) {
                     WorkflowUtils.fail(wrk, c);
                     throw e;
diff --git a/src/main/java/org/nrg/xnat/turbine/modules/actions/ModifyProject.java b/src/main/java/org/nrg/xnat/turbine/modules/actions/ModifyProject.java
old mode 100644
new mode 100755
index b41161bb41fa96924d933ae2c27fe424529bb435..d4f03a9e278f2695559ca0796c2754fbecabcaf0
--- a/src/main/java/org/nrg/xnat/turbine/modules/actions/ModifyProject.java
+++ b/src/main/java/org/nrg/xnat/turbine/modules/actions/ModifyProject.java
@@ -27,7 +27,7 @@ import org.nrg.xft.XFTItem;
 import org.nrg.xft.db.PoolDBUtils;
 import org.nrg.xft.event.XftItemEvent;
 import org.nrg.xft.event.EventMetaI;
-import org.nrg.xft.event.ReactorEventUtils;
+import org.nrg.xft.event.XftEventService;
 import org.nrg.xft.event.EventUtils;
 import org.nrg.xft.event.persist.PersistentWorkflowI;
 import org.nrg.xft.event.persist.PersistentWorkflowUtils;
@@ -130,7 +130,7 @@ public class ModifyProject extends SecureAction {
 
             WorkflowUtils.complete(wrk, c);
             Users.clearCache(user);
-            ReactorEventUtils.triggerEvent(new XftItemEvent(XnatProjectdata.SCHEMA_ELEMENT_NAME, postSave.getId(), XftItemEvent.UPDATE));
+            XftEventService.getService().triggerEvent(new XftItemEvent(XnatProjectdata.SCHEMA_ELEMENT_NAME, postSave.getId(), XftItemEvent.UPDATE));
         } catch (SecurityException e) {
             logger.error("Security exception triggered by user '" + user.getLogin() + "': " + e.getMessage(), e);
             handleException(data, project.getItem(), e, TurbineUtils.EDIT_ITEM);
diff --git a/src/main/java/org/nrg/xnat/utils/CatalogUtils.java b/src/main/java/org/nrg/xnat/utils/CatalogUtils.java
old mode 100644
new mode 100755
index cc8059f9bba7db6dc644542acb962e09e31916a4..795973a032f7dfa3cc5cebe3e482200a3b3fd2d9
--- a/src/main/java/org/nrg/xnat/utils/CatalogUtils.java
+++ b/src/main/java/org/nrg/xnat/utils/CatalogUtils.java
@@ -32,6 +32,7 @@ import org.nrg.xft.utils.zip.ZipI;
 import org.nrg.xft.utils.zip.ZipUtils;
 import org.nrg.xnat.helpers.resource.XnatResourceInfo;
 import org.nrg.xnat.presentation.ChangeSummaryBuilderA;
+import org.nrg.xnat.restlet.files.utils.RestFileUtils;
 import org.nrg.xnat.restlet.util.FileWriterWrapperI;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -558,10 +559,7 @@ public class CatalogUtils {
                 filename = filename.substring(index + 1);
             }
 
-            String compression_method = ".zip";
-            if (filename.contains(".")) {
-                compression_method = filename.substring(filename.lastIndexOf("."));
-            }
+            String compression_method = (filename.contains(".")) ? filename.substring(filename.lastIndexOf(".")) : "";
 
             if (extract && (compression_method.equalsIgnoreCase(".tar") || compression_method.equalsIgnoreCase(".gz") || compression_method.equalsIgnoreCase(".zip") || compression_method.equalsIgnoreCase(".zar"))) {
                 if (logger.isDebugEnabled()) {
@@ -652,6 +650,12 @@ public class CatalogUtils {
                         }
 
                     } else {
+           				// Uploading directories via linux (and likely Mac) will not fail due to "Everything is a file".  In such cases we wind 
+           				// up with a "file" generated.  Check for these "files".  Remove them, and thrown an exception.
+           				// Windows uploads of directories should fail before hitting this class.
+                    	if (RestFileUtils.isFileRepresentationOfDirectory(saveTo) && saveTo.delete()) {
+                    		throw new Exception("Upload of directories/folders is not supported.  The uploaded file appears to have been a directory.");
+                    	}
                         if (logger.isDebugEnabled()) {
                             logger.debug("Updating catalog entry for file " + saveTo.getAbsolutePath());
                         }
diff --git a/src/main/webapp/WEB-INF/conf/InstanceSettings.xml b/src/main/webapp/WEB-INF/conf/InstanceSettings.xml
index a1ae1fb6c453dd766fcad64b1e41285fba39f470..bdf729405a85334d3ea37321b6db153b173431d2 100644
--- a/src/main/webapp/WEB-INF/conf/InstanceSettings.xml
+++ b/src/main/webapp/WEB-INF/conf/InstanceSettings.xml
@@ -1,22 +1,18 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<Instance_Settings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="schemas/xdat/instance.xsd"
-				   site_url="http://localhost:8080" admin_email="administrator@xnat.org" archive_root_path="/data/xnat/archive"
-				   prearchive_path="/data/xnat/prearchive" cache_path="/data/xnat/cache"
-				   smtp_server="irony.wusm.wustl.edu" ftp_path="/data/xnat/ftp" build_path="/data/xnat/build"
-				   pipeline_path="/data/xnat/pipeline" require_login="true" user_registration="false" enable_csrf_token="true">
+<Instance_Settings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="schemas/xdat/instance.xsd" site_url="http://xnatweb.dev" admin_email="hodgem@wustl.edu" archive_root_path="/data/xnatweb/archive" prearchive_path="/data/xnatweb/prearchive" cache_path="/data/xnatweb/cache" smtp_server="irony.wusm.wustl.edu" ftp_path="/data/xnatweb/ftp" build_path="/data/xnatweb/build" pipeline_path="/data/xnatweb/pipeline" require_login="true" user_registration="false" enable_csrf_token="true">
 	<Databases>
-		<Database Type="POSTGRESQL" Id="xnat" Driver="org.postgresql.Driver" Url="jdbc:postgresql://localhost/xnat" User="xnat" Pass="xnat" MaxConnections="10"/>
+		<Database Type="POSTGRESQL" Id="xnatweb" Driver="org.postgresql.Driver" Url="jdbc:postgresql://localhost/xnatweb" User="xnat" Pass="xnat" MaxConnections="10"/>
 	</Databases>
 	<Models>
-		<Data_Model File_Name="security.xsd" File_Location="schemas/security" DB="xnat"/>
-		<Data_Model File_Name="birnprov.xsd" File_Location="schemas/birn" DB="xnat"/>
-		<Data_Model File_Name="xnat.xsd" File_Location="schemas/xnat" DB="xnat"/>
-		<Data_Model File_Name="workflow.xsd" File_Location="schemas/pipeline" DB="xnat"/>
-		<Data_Model File_Name="repository.xsd" File_Location="schemas/pipeline" DB="xnat"/>
-		<Data_Model File_Name="project.xsd" File_Location="schemas/project" DB="xnat"/>
-		<Data_Model File_Name="assessments.xsd" File_Location="schemas/assessments" DB="xnat"/>
-		<Data_Model File_Name="catalog.xsd" File_Location="schemas/catalog" DB="xnat"/>
-		<Data_Model File_Name="protocolValidation.xsd" File_Location="schemas/validation" DB="xnat"/>
-		<Data_Model File_Name="screeningAssessment.xsd" File_Location="schemas/screening" DB="xnat"/>
+		<Data_Model File_Name="security.xsd" File_Location="schemas/security" DB="xnatweb"/>
+		<Data_Model File_Name="birnprov.xsd" File_Location="schemas/birn" DB="xnatweb"/>
+		<Data_Model File_Name="xnat.xsd" File_Location="schemas/xnat" DB="xnatweb"/>
+		<Data_Model File_Name="workflow.xsd" File_Location="schemas/pipeline" DB="xnatweb"/>
+		<Data_Model File_Name="repository.xsd" File_Location="schemas/pipeline" DB="xnatweb"/>
+		<Data_Model File_Name="project.xsd" File_Location="schemas/project" DB="xnatweb"/>
+		<Data_Model File_Name="assessments.xsd" File_Location="schemas/assessments" DB="xnatweb"/>
+		<Data_Model File_Name="catalog.xsd" File_Location="schemas/catalog" DB="xnatweb"/>
+		<Data_Model File_Name="protocolValidation.xsd" File_Location="schemas/validation" DB="xnatweb"/>
+		<Data_Model File_Name="screeningAssessment.xsd" File_Location="schemas/screening" DB="xnatweb"/>
 	</Models>
 </Instance_Settings>
diff --git a/src/main/webapp/scripts/lib/jquery-plugins/jquery.form.js b/src/main/webapp/scripts/lib/jquery-plugins/jquery.form.js
new file mode 100644
index 0000000000000000000000000000000000000000..591ad6f1fe7b5a436387870822f7e0189989cab7
--- /dev/null
+++ b/src/main/webapp/scripts/lib/jquery-plugins/jquery.form.js
@@ -0,0 +1,1277 @@
+/*!
+ * jQuery Form Plugin
+ * version: 3.51.0-2014.06.20
+ * Requires jQuery v1.5 or later
+ * Copyright (c) 2014 M. Alsup
+ * Examples and documentation at: http://malsup.com/jquery/form/
+ * Project repository: https://github.com/malsup/form
+ * Dual licensed under the MIT and GPL licenses.
+ * https://github.com/malsup/form#copyright-and-license
+ */
+/*global ActiveXObject */
+
+// AMD support
+(function (factory) {
+    "use strict";
+    if (typeof define === 'function' && define.amd) {
+        // using AMD; register as anon module
+        define(['jquery'], factory);
+    } else {
+        // no AMD; invoke directly
+        factory( (typeof(jQuery) != 'undefined') ? jQuery : window.Zepto );
+    }
+}
+
+(function($) {
+"use strict";
+
+/*
+    Usage Note:
+    -----------
+    Do not use both ajaxSubmit and ajaxForm on the same form.  These
+    functions are mutually exclusive.  Use ajaxSubmit if you want
+    to bind your own submit handler to the form.  For example,
+
+    $(document).ready(function() {
+        $('#myForm').on('submit', function(e) {
+            e.preventDefault(); // <-- important
+            $(this).ajaxSubmit({
+                target: '#output'
+            });
+        });
+    });
+
+    Use ajaxForm when you want the plugin to manage all the event binding
+    for you.  For example,
+
+    $(document).ready(function() {
+        $('#myForm').ajaxForm({
+            target: '#output'
+        });
+    });
+
+    You can also use ajaxForm with delegation (requires jQuery v1.7+), so the
+    form does not have to exist when you invoke ajaxForm:
+
+    $('#myForm').ajaxForm({
+        delegation: true,
+        target: '#output'
+    });
+
+    When using ajaxForm, the ajaxSubmit function will be invoked for you
+    at the appropriate time.
+*/
+
+/**
+ * Feature detection
+ */
+var feature = {};
+feature.fileapi = $("<input type='file'/>").get(0).files !== undefined;
+feature.formdata = window.FormData !== undefined;
+
+var hasProp = !!$.fn.prop;
+
+// attr2 uses prop when it can but checks the return type for
+// an expected string.  this accounts for the case where a form 
+// contains inputs with names like "action" or "method"; in those
+// cases "prop" returns the element
+$.fn.attr2 = function() {
+    if ( ! hasProp ) {
+        return this.attr.apply(this, arguments);
+    }
+    var val = this.prop.apply(this, arguments);
+    if ( ( val && val.jquery ) || typeof val === 'string' ) {
+        return val;
+    }
+    return this.attr.apply(this, arguments);
+};
+
+/**
+ * ajaxSubmit() provides a mechanism for immediately submitting
+ * an HTML form using AJAX.
+ */
+$.fn.ajaxSubmit = function(options) {
+    /*jshint scripturl:true */
+
+    // fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
+    if (!this.length) {
+        log('ajaxSubmit: skipping submit process - no element selected');
+        return this;
+    }
+
+    var method, action, url, $form = this;
+
+    if (typeof options == 'function') {
+        options = { success: options };
+    }
+    else if ( options === undefined ) {
+        options = {};
+    }
+
+    method = options.type || this.attr2('method');
+    action = options.url  || this.attr2('action');
+
+    url = (typeof action === 'string') ? $.trim(action) : '';
+    url = url || window.location.href || '';
+    if (url) {
+        // clean url (don't include hash vaue)
+        url = (url.match(/^([^#]+)/)||[])[1];
+    }
+
+    options = $.extend(true, {
+        url:  url,
+        success: $.ajaxSettings.success,
+        type: method || $.ajaxSettings.type,
+        iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank'
+    }, options);
+
+    // hook for manipulating the form data before it is extracted;
+    // convenient for use with rich editors like tinyMCE or FCKEditor
+    var veto = {};
+    this.trigger('form-pre-serialize', [this, options, veto]);
+    if (veto.veto) {
+        log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
+        return this;
+    }
+
+    // provide opportunity to alter form data before it is serialized
+    if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
+        log('ajaxSubmit: submit aborted via beforeSerialize callback');
+        return this;
+    }
+
+    var traditional = options.traditional;
+    if ( traditional === undefined ) {
+        traditional = $.ajaxSettings.traditional;
+    }
+
+    var elements = [];
+    var qx, a = this.formToArray(options.semantic, elements);
+    if (options.data) {
+        options.extraData = options.data;
+        qx = $.param(options.data, traditional);
+    }
+
+    // give pre-submit callback an opportunity to abort the submit
+    if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
+        log('ajaxSubmit: submit aborted via beforeSubmit callback');
+        return this;
+    }
+
+    // fire vetoable 'validate' event
+    this.trigger('form-submit-validate', [a, this, options, veto]);
+    if (veto.veto) {
+        log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
+        return this;
+    }
+
+    var q = $.param(a, traditional);
+    if (qx) {
+        q = ( q ? (q + '&' + qx) : qx );
+    }
+    if (options.type.toUpperCase() == 'GET') {
+        options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
+        options.data = null;  // data is null for 'get'
+    }
+    else {
+        options.data = q; // data is the query string for 'post'
+    }
+
+    var callbacks = [];
+    if (options.resetForm) {
+        callbacks.push(function() { $form.resetForm(); });
+    }
+    if (options.clearForm) {
+        callbacks.push(function() { $form.clearForm(options.includeHidden); });
+    }
+
+    // perform a load on the target only if dataType is not provided
+    if (!options.dataType && options.target) {
+        var oldSuccess = options.success || function(){};
+        callbacks.push(function(data) {
+            var fn = options.replaceTarget ? 'replaceWith' : 'html';
+            $(options.target)[fn](data).each(oldSuccess, arguments);
+        });
+    }
+    else if (options.success) {
+        callbacks.push(options.success);
+    }
+
+    options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg
+        var context = options.context || this ;    // jQuery 1.4+ supports scope context
+        for (var i=0, max=callbacks.length; i < max; i++) {
+            callbacks[i].apply(context, [data, status, xhr || $form, $form]);
+        }
+    };
+
+    if (options.error) {
+        var oldError = options.error;
+        options.error = function(xhr, status, error) {
+            var context = options.context || this;
+            oldError.apply(context, [xhr, status, error, $form]);
+        };
+    }
+
+     if (options.complete) {
+        var oldComplete = options.complete;
+        options.complete = function(xhr, status) {
+            var context = options.context || this;
+            oldComplete.apply(context, [xhr, status, $form]);
+        };
+    }
+
+    // are there files to upload?
+
+    // [value] (issue #113), also see comment:
+    // https://github.com/malsup/form/commit/588306aedba1de01388032d5f42a60159eea9228#commitcomment-2180219
+    var fileInputs = $('input[type=file]:enabled', this).filter(function() { return $(this).val() !== ''; });
+
+    var hasFileInputs = fileInputs.length > 0;
+    var mp = 'multipart/form-data';
+    var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);
+
+    var fileAPI = feature.fileapi && feature.formdata;
+    log("fileAPI :" + fileAPI);
+    var shouldUseFrame = (hasFileInputs || multipart) && !fileAPI;
+
+    var jqxhr;
+
+    // options.iframe allows user to force iframe mode
+    // 06-NOV-09: now defaulting to iframe mode if file input is detected
+    if (options.iframe !== false && (options.iframe || shouldUseFrame)) {
+        // hack to fix Safari hang (thanks to Tim Molendijk for this)
+        // see:  http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
+        if (options.closeKeepAlive) {
+            $.get(options.closeKeepAlive, function() {
+                jqxhr = fileUploadIframe(a);
+            });
+        }
+        else {
+            jqxhr = fileUploadIframe(a);
+        }
+    }
+    else if ((hasFileInputs || multipart) && fileAPI) {
+        jqxhr = fileUploadXhr(a);
+    }
+    else {
+        jqxhr = $.ajax(options);
+    }
+
+    $form.removeData('jqxhr').data('jqxhr', jqxhr);
+
+    // clear element array
+    for (var k=0; k < elements.length; k++) {
+        elements[k] = null;
+    }
+
+    // fire 'notify' event
+    this.trigger('form-submit-notify', [this, options]);
+    return this;
+
+    // utility fn for deep serialization
+    function deepSerialize(extraData){
+        var serialized = $.param(extraData, options.traditional).split('&');
+        var len = serialized.length;
+        var result = [];
+        var i, part;
+        for (i=0; i < len; i++) {
+            // #252; undo param space replacement
+            serialized[i] = serialized[i].replace(/\+/g,' ');
+            part = serialized[i].split('=');
+            // #278; use array instead of object storage, favoring array serializations
+            result.push([decodeURIComponent(part[0]), decodeURIComponent(part[1])]);
+        }
+        return result;
+    }
+
+     // XMLHttpRequest Level 2 file uploads (big hat tip to francois2metz)
+    function fileUploadXhr(a) {
+        var formdata = new FormData();
+
+        for (var i=0; i < a.length; i++) {
+            formdata.append(a[i].name, a[i].value);
+        }
+
+        if (options.extraData) {
+            var serializedData = deepSerialize(options.extraData);
+            for (i=0; i < serializedData.length; i++) {
+                if (serializedData[i]) {
+                    formdata.append(serializedData[i][0], serializedData[i][1]);
+                }
+            }
+        }
+
+        options.data = null;
+
+        var s = $.extend(true, {}, $.ajaxSettings, options, {
+            contentType: false,
+            processData: false,
+            cache: false,
+            type: method || 'POST'
+        });
+
+        if (options.uploadProgress) {
+            // workaround because jqXHR does not expose upload property
+            s.xhr = function() {
+                var xhr = $.ajaxSettings.xhr();
+                if (xhr.upload) {
+                    xhr.upload.addEventListener('progress', function(event) {
+                        var percent = 0;
+                        var position = event.loaded || event.position; /*event.position is deprecated*/
+                        var total = event.total;
+                        if (event.lengthComputable) {
+                            percent = Math.ceil(position / total * 100);
+                        }
+                        options.uploadProgress(event, position, total, percent);
+                    }, false);
+                }
+                return xhr;
+            };
+        }
+
+        s.data = null;
+        var beforeSend = s.beforeSend;
+        s.beforeSend = function(xhr, o) {
+            //Send FormData() provided by user
+            if (options.formData) {
+                o.data = options.formData;
+            }
+            else {
+                o.data = formdata;
+            }
+            if(beforeSend) {
+                beforeSend.call(this, xhr, o);
+            }
+        };
+        return $.ajax(s);
+    }
+
+    // private function for handling file uploads (hat tip to YAHOO!)
+    function fileUploadIframe(a) {
+        var form = $form[0], el, i, s, g, id, $io, io, xhr, sub, n, timedOut, timeoutHandle;
+        var deferred = $.Deferred();
+
+        // #341
+        deferred.abort = function(status) {
+            xhr.abort(status);
+        };
+
+        if (a) {
+            // ensure that every serialized input is still enabled
+            for (i=0; i < elements.length; i++) {
+                el = $(elements[i]);
+                if ( hasProp ) {
+                    el.prop('disabled', false);
+                }
+                else {
+                    el.removeAttr('disabled');
+                }
+            }
+        }
+
+        s = $.extend(true, {}, $.ajaxSettings, options);
+        s.context = s.context || s;
+        id = 'jqFormIO' + (new Date().getTime());
+        if (s.iframeTarget) {
+            $io = $(s.iframeTarget);
+            n = $io.attr2('name');
+            if (!n) {
+                $io.attr2('name', id);
+            }
+            else {
+                id = n;
+            }
+        }
+        else {
+            $io = $('<iframe name="' + id + '" src="'+ s.iframeSrc +'" />');
+            $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
+        }
+        io = $io[0];
+
+
+        xhr = { // mock object
+            aborted: 0,
+            responseText: null,
+            responseXML: null,
+            status: 0,
+            statusText: 'n/a',
+            getAllResponseHeaders: function() {},
+            getResponseHeader: function() {},
+            setRequestHeader: function() {},
+            abort: function(status) {
+                var e = (status === 'timeout' ? 'timeout' : 'aborted');
+                log('aborting upload... ' + e);
+                this.aborted = 1;
+
+                try { // #214, #257
+                    if (io.contentWindow.document.execCommand) {
+                        io.contentWindow.document.execCommand('Stop');
+                    }
+                }
+                catch(ignore) {}
+
+                $io.attr('src', s.iframeSrc); // abort op in progress
+                xhr.error = e;
+                if (s.error) {
+                    s.error.call(s.context, xhr, e, status);
+                }
+                if (g) {
+                    $.event.trigger("ajaxError", [xhr, s, e]);
+                }
+                if (s.complete) {
+                    s.complete.call(s.context, xhr, e);
+                }
+            }
+        };
+
+        g = s.global;
+        // trigger ajax global events so that activity/block indicators work like normal
+        if (g && 0 === $.active++) {
+            $.event.trigger("ajaxStart");
+        }
+        if (g) {
+            $.event.trigger("ajaxSend", [xhr, s]);
+        }
+
+        if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) {
+            if (s.global) {
+                $.active--;
+            }
+            deferred.reject();
+            return deferred;
+        }
+        if (xhr.aborted) {
+            deferred.reject();
+            return deferred;
+        }
+
+        // add submitting element to data if we know it
+        sub = form.clk;
+        if (sub) {
+            n = sub.name;
+            if (n && !sub.disabled) {
+                s.extraData = s.extraData || {};
+                s.extraData[n] = sub.value;
+                if (sub.type == "image") {
+                    s.extraData[n+'.x'] = form.clk_x;
+                    s.extraData[n+'.y'] = form.clk_y;
+                }
+            }
+        }
+
+        var CLIENT_TIMEOUT_ABORT = 1;
+        var SERVER_ABORT = 2;
+                
+        function getDoc(frame) {
+            /* it looks like contentWindow or contentDocument do not
+             * carry the protocol property in ie8, when running under ssl
+             * frame.document is the only valid response document, since
+             * the protocol is know but not on the other two objects. strange?
+             * "Same origin policy" http://en.wikipedia.org/wiki/Same_origin_policy
+             */
+            
+            var doc = null;
+            
+            // IE8 cascading access check
+            try {
+                if (frame.contentWindow) {
+                    doc = frame.contentWindow.document;
+                }
+            } catch(err) {
+                // IE8 access denied under ssl & missing protocol
+                log('cannot get iframe.contentWindow document: ' + err);
+            }
+
+            if (doc) { // successful getting content
+                return doc;
+            }
+
+            try { // simply checking may throw in ie8 under ssl or mismatched protocol
+                doc = frame.contentDocument ? frame.contentDocument : frame.document;
+            } catch(err) {
+                // last attempt
+                log('cannot get iframe.contentDocument: ' + err);
+                doc = frame.document;
+            }
+            return doc;
+        }
+
+        // Rails CSRF hack (thanks to Yvan Barthelemy)
+        var csrf_token = $('meta[name=csrf-token]').attr('content');
+        var csrf_param = $('meta[name=csrf-param]').attr('content');
+        if (csrf_param && csrf_token) {
+            s.extraData = s.extraData || {};
+            s.extraData[csrf_param] = csrf_token;
+        }
+
+        // take a breath so that pending repaints get some cpu time before the upload starts
+        function doSubmit() {
+            // make sure form attrs are set
+            var t = $form.attr2('target'), 
+                a = $form.attr2('action'), 
+                mp = 'multipart/form-data',
+                et = $form.attr('enctype') || $form.attr('encoding') || mp;
+
+            // update form attrs in IE friendly way
+            form.setAttribute('target',id);
+            if (!method || /post/i.test(method) ) {
+                form.setAttribute('method', 'POST');
+            }
+            if (a != s.url) {
+                form.setAttribute('action', s.url);
+            }
+
+            // ie borks in some cases when setting encoding
+            if (! s.skipEncodingOverride && (!method || /post/i.test(method))) {
+                $form.attr({
+                    encoding: 'multipart/form-data',
+                    enctype:  'multipart/form-data'
+                });
+            }
+
+            // support timout
+            if (s.timeout) {
+                timeoutHandle = setTimeout(function() { timedOut = true; cb(CLIENT_TIMEOUT_ABORT); }, s.timeout);
+            }
+
+            // look for server aborts
+            function checkState() {
+                try {
+                    var state = getDoc(io).readyState;
+                    log('state = ' + state);
+                    if (state && state.toLowerCase() == 'uninitialized') {
+                        setTimeout(checkState,50);
+                    }
+                }
+                catch(e) {
+                    log('Server abort: ' , e, ' (', e.name, ')');
+                    cb(SERVER_ABORT);
+                    if (timeoutHandle) {
+                        clearTimeout(timeoutHandle);
+                    }
+                    timeoutHandle = undefined;
+                }
+            }
+
+            // add "extra" data to form if provided in options
+            var extraInputs = [];
+            try {
+                if (s.extraData) {
+                    for (var n in s.extraData) {
+                        if (s.extraData.hasOwnProperty(n)) {
+                           // if using the $.param format that allows for multiple values with the same name
+                           if($.isPlainObject(s.extraData[n]) && s.extraData[n].hasOwnProperty('name') && s.extraData[n].hasOwnProperty('value')) {
+                               extraInputs.push(
+                               $('<input type="hidden" name="'+s.extraData[n].name+'">').val(s.extraData[n].value)
+                                   .appendTo(form)[0]);
+                           } else {
+                               extraInputs.push(
+                               $('<input type="hidden" name="'+n+'">').val(s.extraData[n])
+                                   .appendTo(form)[0]);
+                           }
+                        }
+                    }
+                }
+
+                if (!s.iframeTarget) {
+                    // add iframe to doc and submit the form
+                    $io.appendTo('body');
+                }
+                if (io.attachEvent) {
+                    io.attachEvent('onload', cb);
+                }
+                else {
+                    io.addEventListener('load', cb, false);
+                }
+                setTimeout(checkState,15);
+
+                try {
+                    form.submit();
+                } catch(err) {
+                    // just in case form has element with name/id of 'submit'
+                    var submitFn = document.createElement('form').submit;
+                    submitFn.apply(form);
+                }
+            }
+            finally {
+                // reset attrs and remove "extra" input elements
+                form.setAttribute('action',a);
+                form.setAttribute('enctype', et); // #380
+                if(t) {
+                    form.setAttribute('target', t);
+                } else {
+                    $form.removeAttr('target');
+                }
+                $(extraInputs).remove();
+            }
+        }
+
+        if (s.forceSync) {
+            doSubmit();
+        }
+        else {
+            setTimeout(doSubmit, 10); // this lets dom updates render
+        }
+
+        var data, doc, domCheckCount = 50, callbackProcessed;
+
+        function cb(e) {
+            if (xhr.aborted || callbackProcessed) {
+                return;
+            }
+            
+            doc = getDoc(io);
+            if(!doc) {
+                log('cannot access response document');
+                e = SERVER_ABORT;
+            }
+            if (e === CLIENT_TIMEOUT_ABORT && xhr) {
+                xhr.abort('timeout');
+                deferred.reject(xhr, 'timeout');
+                return;
+            }
+            else if (e == SERVER_ABORT && xhr) {
+                xhr.abort('server abort');
+                deferred.reject(xhr, 'error', 'server abort');
+                return;
+            }
+
+            if (!doc || doc.location.href == s.iframeSrc) {
+                // response not received yet
+                if (!timedOut) {
+                    return;
+                }
+            }
+            if (io.detachEvent) {
+                io.detachEvent('onload', cb);
+            }
+            else {
+                io.removeEventListener('load', cb, false);
+            }
+
+            var status = 'success', errMsg;
+            try {
+                if (timedOut) {
+                    throw 'timeout';
+                }
+
+                var isXml = s.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc);
+                log('isXml='+isXml);
+                if (!isXml && window.opera && (doc.body === null || !doc.body.innerHTML)) {
+                    if (--domCheckCount) {
+                        // in some browsers (Opera) the iframe DOM is not always traversable when
+                        // the onload callback fires, so we loop a bit to accommodate
+                        log('requeing onLoad callback, DOM not available');
+                        setTimeout(cb, 250);
+                        return;
+                    }
+                    // let this fall through because server response could be an empty document
+                    //log('Could not access iframe DOM after mutiple tries.');
+                    //throw 'DOMException: not available';
+                }
+
+                //log('response detected');
+                var docRoot = doc.body ? doc.body : doc.documentElement;
+                xhr.responseText = docRoot ? docRoot.innerHTML : null;
+                xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
+                if (isXml) {
+                    s.dataType = 'xml';
+                }
+                xhr.getResponseHeader = function(header){
+                    var headers = {'content-type': s.dataType};
+                    return headers[header.toLowerCase()];
+                };
+                // support for XHR 'status' & 'statusText' emulation :
+                if (docRoot) {
+                    xhr.status = Number( docRoot.getAttribute('status') ) || xhr.status;
+                    xhr.statusText = docRoot.getAttribute('statusText') || xhr.statusText;
+                }
+
+                var dt = (s.dataType || '').toLowerCase();
+                var scr = /(json|script|text)/.test(dt);
+                if (scr || s.textarea) {
+                    // see if user embedded response in textarea
+                    var ta = doc.getElementsByTagName('textarea')[0];
+                    if (ta) {
+                        xhr.responseText = ta.value;
+                        // support for XHR 'status' & 'statusText' emulation :
+                        xhr.status = Number( ta.getAttribute('status') ) || xhr.status;
+                        xhr.statusText = ta.getAttribute('statusText') || xhr.statusText;
+                    }
+                    else if (scr) {
+                        // account for browsers injecting pre around json response
+                        var pre = doc.getElementsByTagName('pre')[0];
+                        var b = doc.getElementsByTagName('body')[0];
+                        if (pre) {
+                            xhr.responseText = pre.textContent ? pre.textContent : pre.innerText;
+                        }
+                        else if (b) {
+                            xhr.responseText = b.textContent ? b.textContent : b.innerText;
+                        }
+                    }
+                }
+                else if (dt == 'xml' && !xhr.responseXML && xhr.responseText) {
+                    xhr.responseXML = toXml(xhr.responseText);
+                }
+
+                try {
+                    data = httpData(xhr, dt, s);
+                }
+                catch (err) {
+                    status = 'parsererror';
+                    xhr.error = errMsg = (err || status);
+                }
+            }
+            catch (err) {
+                log('error caught: ',err);
+                status = 'error';
+                xhr.error = errMsg = (err || status);
+            }
+
+            if (xhr.aborted) {
+                log('upload aborted');
+                status = null;
+            }
+
+            if (xhr.status) { // we've set xhr.status
+                status = (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) ? 'success' : 'error';
+            }
+
+            // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
+            if (status === 'success') {
+                if (s.success) {
+                    s.success.call(s.context, data, 'success', xhr);
+                }
+                deferred.resolve(xhr.responseText, 'success', xhr);
+                if (g) {
+                    $.event.trigger("ajaxSuccess", [xhr, s]);
+                }
+            }
+            else if (status) {
+                if (errMsg === undefined) {
+                    errMsg = xhr.statusText;
+                }
+                if (s.error) {
+                    s.error.call(s.context, xhr, status, errMsg);
+                }
+                deferred.reject(xhr, 'error', errMsg);
+                if (g) {
+                    $.event.trigger("ajaxError", [xhr, s, errMsg]);
+                }
+            }
+
+            if (g) {
+                $.event.trigger("ajaxComplete", [xhr, s]);
+            }
+
+            if (g && ! --$.active) {
+                $.event.trigger("ajaxStop");
+            }
+
+            if (s.complete) {
+                s.complete.call(s.context, xhr, status);
+            }
+
+            callbackProcessed = true;
+            if (s.timeout) {
+                clearTimeout(timeoutHandle);
+            }
+
+            // clean up
+            setTimeout(function() {
+                if (!s.iframeTarget) {
+                    $io.remove();
+                }
+                else { //adding else to clean up existing iframe response.
+                    $io.attr('src', s.iframeSrc);
+                }
+                xhr.responseXML = null;
+            }, 100);
+        }
+
+        var toXml = $.parseXML || function(s, doc) { // use parseXML if available (jQuery 1.5+)
+            if (window.ActiveXObject) {
+                doc = new ActiveXObject('Microsoft.XMLDOM');
+                doc.async = 'false';
+                doc.loadXML(s);
+            }
+            else {
+                doc = (new DOMParser()).parseFromString(s, 'text/xml');
+            }
+            return (doc && doc.documentElement && doc.documentElement.nodeName != 'parsererror') ? doc : null;
+        };
+        var parseJSON = $.parseJSON || function(s) {
+            /*jslint evil:true */
+            return window['eval']('(' + s + ')');
+        };
+
+        var httpData = function( xhr, type, s ) { // mostly lifted from jq1.4.4
+
+            var ct = xhr.getResponseHeader('content-type') || '',
+                xml = type === 'xml' || !type && ct.indexOf('xml') >= 0,
+                data = xml ? xhr.responseXML : xhr.responseText;
+
+            if (xml && data.documentElement.nodeName === 'parsererror') {
+                if ($.error) {
+                    $.error('parsererror');
+                }
+            }
+            if (s && s.dataFilter) {
+                data = s.dataFilter(data, type);
+            }
+            if (typeof data === 'string') {
+                if (type === 'json' || !type && ct.indexOf('json') >= 0) {
+                    data = parseJSON(data);
+                } else if (type === "script" || !type && ct.indexOf("javascript") >= 0) {
+                    $.globalEval(data);
+                }
+            }
+            return data;
+        };
+
+        return deferred;
+    }
+};
+
+/**
+ * ajaxForm() provides a mechanism for fully automating form submission.
+ *
+ * The advantages of using this method instead of ajaxSubmit() are:
+ *
+ * 1: This method will include coordinates for <input type="image" /> elements (if the element
+ *    is used to submit the form).
+ * 2. This method will include the submit element's name/value data (for the element that was
+ *    used to submit the form).
+ * 3. This method binds the submit() method to the form for you.
+ *
+ * The options argument for ajaxForm works exactly as it does for ajaxSubmit.  ajaxForm merely
+ * passes the options argument along after properly binding events for submit elements and
+ * the form itself.
+ */
+$.fn.ajaxForm = function(options) {
+    options = options || {};
+    options.delegation = options.delegation && $.isFunction($.fn.on);
+
+    // in jQuery 1.3+ we can fix mistakes with the ready state
+    if (!options.delegation && this.length === 0) {
+        var o = { s: this.selector, c: this.context };
+        if (!$.isReady && o.s) {
+            log('DOM not ready, queuing ajaxForm');
+            $(function() {
+                $(o.s,o.c).ajaxForm(options);
+            });
+            return this;
+        }
+        // is your DOM ready?  http://docs.jquery.com/Tutorials:Introducing_$(document).ready()
+        log('terminating; zero elements found by selector' + ($.isReady ? '' : ' (DOM not ready)'));
+        return this;
+    }
+
+    if ( options.delegation ) {
+        $(document)
+            .off('submit.form-plugin', this.selector, doAjaxSubmit)
+            .off('click.form-plugin', this.selector, captureSubmittingElement)
+            .on('submit.form-plugin', this.selector, options, doAjaxSubmit)
+            .on('click.form-plugin', this.selector, options, captureSubmittingElement);
+        return this;
+    }
+
+    return this.ajaxFormUnbind()
+        .bind('submit.form-plugin', options, doAjaxSubmit)
+        .bind('click.form-plugin', options, captureSubmittingElement);
+};
+
+// private event handlers
+function doAjaxSubmit(e) {
+    /*jshint validthis:true */
+    var options = e.data;
+    if (!e.isDefaultPrevented()) { // if event has been canceled, don't proceed
+        e.preventDefault();
+        $(e.target).ajaxSubmit(options); // #365
+    }
+}
+
+function captureSubmittingElement(e) {
+    /*jshint validthis:true */
+    var target = e.target;
+    var $el = $(target);
+    if (!($el.is("[type=submit],[type=image]"))) {
+        // is this a child element of the submit el?  (ex: a span within a button)
+        var t = $el.closest('[type=submit]');
+        if (t.length === 0) {
+            return;
+        }
+        target = t[0];
+    }
+    var form = this;
+    form.clk = target;
+    if (target.type == 'image') {
+        if (e.offsetX !== undefined) {
+            form.clk_x = e.offsetX;
+            form.clk_y = e.offsetY;
+        } else if (typeof $.fn.offset == 'function') {
+            var offset = $el.offset();
+            form.clk_x = e.pageX - offset.left;
+            form.clk_y = e.pageY - offset.top;
+        } else {
+            form.clk_x = e.pageX - target.offsetLeft;
+            form.clk_y = e.pageY - target.offsetTop;
+        }
+    }
+    // clear form vars
+    setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 100);
+}
+
+
+// ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
+$.fn.ajaxFormUnbind = function() {
+    return this.unbind('submit.form-plugin click.form-plugin');
+};
+
+/**
+ * formToArray() gathers form element data into an array of objects that can
+ * be passed to any of the following ajax functions: $.get, $.post, or load.
+ * Each object in the array has both a 'name' and 'value' property.  An example of
+ * an array for a simple login form might be:
+ *
+ * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
+ *
+ * It is this array that is passed to pre-submit callback functions provided to the
+ * ajaxSubmit() and ajaxForm() methods.
+ */
+$.fn.formToArray = function(semantic, elements) {
+    var a = [];
+    if (this.length === 0) {
+        return a;
+    }
+
+    var form = this[0];
+    var formId = this.attr('id');
+    var els = semantic ? form.getElementsByTagName('*') : form.elements;
+    var els2;
+
+    if (els && !/MSIE [678]/.test(navigator.userAgent)) { // #390
+        els = $(els).get();  // convert to standard array
+    }
+
+    // #386; account for inputs outside the form which use the 'form' attribute
+    if ( formId ) {
+        els2 = $(':input[form="' + formId + '"]').get(); // hat tip @thet
+        if ( els2.length ) {
+            els = (els || []).concat(els2);
+        }
+    }
+
+    if (!els || !els.length) {
+        return a;
+    }
+
+    var i,j,n,v,el,max,jmax;
+    for(i=0, max=els.length; i < max; i++) {
+        el = els[i];
+        n = el.name;
+        if (!n || el.disabled) {
+            continue;
+        }
+
+        if (semantic && form.clk && el.type == "image") {
+            // handle image inputs on the fly when semantic == true
+            if(form.clk == el) {
+                a.push({name: n, value: $(el).val(), type: el.type });
+                a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
+            }
+            continue;
+        }
+
+        v = $.fieldValue(el, true);
+        if (v && v.constructor == Array) {
+            if (elements) {
+                elements.push(el);
+            }
+            for(j=0, jmax=v.length; j < jmax; j++) {
+                a.push({name: n, value: v[j]});
+            }
+        }
+        else if (feature.fileapi && el.type == 'file') {
+            if (elements) {
+                elements.push(el);
+            }
+            var files = el.files;
+            if (files.length) {
+                for (j=0; j < files.length; j++) {
+                    a.push({name: n, value: files[j], type: el.type});
+                }
+            }
+            else {
+                // #180
+                a.push({ name: n, value: '', type: el.type });
+            }
+        }
+        else if (v !== null && typeof v != 'undefined') {
+            if (elements) {
+                elements.push(el);
+            }
+            a.push({name: n, value: v, type: el.type, required: el.required});
+        }
+    }
+
+    if (!semantic && form.clk) {
+        // input type=='image' are not found in elements array! handle it here
+        var $input = $(form.clk), input = $input[0];
+        n = input.name;
+        if (n && !input.disabled && input.type == 'image') {
+            a.push({name: n, value: $input.val()});
+            a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
+        }
+    }
+    return a;
+};
+
+/**
+ * Serializes form data into a 'submittable' string. This method will return a string
+ * in the format: name1=value1&amp;name2=value2
+ */
+$.fn.formSerialize = function(semantic) {
+    //hand off to jQuery.param for proper encoding
+    return $.param(this.formToArray(semantic));
+};
+
+/**
+ * Serializes all field elements in the jQuery object into a query string.
+ * This method will return a string in the format: name1=value1&amp;name2=value2
+ */
+$.fn.fieldSerialize = function(successful) {
+    var a = [];
+    this.each(function() {
+        var n = this.name;
+        if (!n) {
+            return;
+        }
+        var v = $.fieldValue(this, successful);
+        if (v && v.constructor == Array) {
+            for (var i=0,max=v.length; i < max; i++) {
+                a.push({name: n, value: v[i]});
+            }
+        }
+        else if (v !== null && typeof v != 'undefined') {
+            a.push({name: this.name, value: v});
+        }
+    });
+    //hand off to jQuery.param for proper encoding
+    return $.param(a);
+};
+
+/**
+ * Returns the value(s) of the element in the matched set.  For example, consider the following form:
+ *
+ *  <form><fieldset>
+ *      <input name="A" type="text" />
+ *      <input name="A" type="text" />
+ *      <input name="B" type="checkbox" value="B1" />
+ *      <input name="B" type="checkbox" value="B2"/>
+ *      <input name="C" type="radio" value="C1" />
+ *      <input name="C" type="radio" value="C2" />
+ *  </fieldset></form>
+ *
+ *  var v = $('input[type=text]').fieldValue();
+ *  // if no values are entered into the text inputs
+ *  v == ['','']
+ *  // if values entered into the text inputs are 'foo' and 'bar'
+ *  v == ['foo','bar']
+ *
+ *  var v = $('input[type=checkbox]').fieldValue();
+ *  // if neither checkbox is checked
+ *  v === undefined
+ *  // if both checkboxes are checked
+ *  v == ['B1', 'B2']
+ *
+ *  var v = $('input[type=radio]').fieldValue();
+ *  // if neither radio is checked
+ *  v === undefined
+ *  // if first radio is checked
+ *  v == ['C1']
+ *
+ * The successful argument controls whether or not the field element must be 'successful'
+ * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
+ * The default value of the successful argument is true.  If this value is false the value(s)
+ * for each element is returned.
+ *
+ * Note: This method *always* returns an array.  If no valid value can be determined the
+ *    array will be empty, otherwise it will contain one or more values.
+ */
+$.fn.fieldValue = function(successful) {
+    for (var val=[], i=0, max=this.length; i < max; i++) {
+        var el = this[i];
+        var v = $.fieldValue(el, successful);
+        if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length)) {
+            continue;
+        }
+        if (v.constructor == Array) {
+            $.merge(val, v);
+        }
+        else {
+            val.push(v);
+        }
+    }
+    return val;
+};
+
+/**
+ * Returns the value of the field element.
+ */
+$.fieldValue = function(el, successful) {
+    var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
+    if (successful === undefined) {
+        successful = true;
+    }
+
+    if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
+        (t == 'checkbox' || t == 'radio') && !el.checked ||
+        (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
+        tag == 'select' && el.selectedIndex == -1)) {
+            return null;
+    }
+
+    if (tag == 'select') {
+        var index = el.selectedIndex;
+        if (index < 0) {
+            return null;
+        }
+        var a = [], ops = el.options;
+        var one = (t == 'select-one');
+        var max = (one ? index+1 : ops.length);
+        for(var i=(one ? index : 0); i < max; i++) {
+            var op = ops[i];
+            if (op.selected) {
+                var v = op.value;
+                if (!v) { // extra pain for IE...
+                    v = (op.attributes && op.attributes.value && !(op.attributes.value.specified)) ? op.text : op.value;
+                }
+                if (one) {
+                    return v;
+                }
+                a.push(v);
+            }
+        }
+        return a;
+    }
+    return $(el).val();
+};
+
+/**
+ * Clears the form data.  Takes the following actions on the form's input fields:
+ *  - input text fields will have their 'value' property set to the empty string
+ *  - select elements will have their 'selectedIndex' property set to -1
+ *  - checkbox and radio inputs will have their 'checked' property set to false
+ *  - inputs of type submit, button, reset, and hidden will *not* be effected
+ *  - button elements will *not* be effected
+ */
+$.fn.clearForm = function(includeHidden) {
+    return this.each(function() {
+        $('input,select,textarea', this).clearFields(includeHidden);
+    });
+};
+
+/**
+ * Clears the selected form elements.
+ */
+$.fn.clearFields = $.fn.clearInputs = function(includeHidden) {
+    var re = /^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i; // 'hidden' is not in this list
+    return this.each(function() {
+        var t = this.type, tag = this.tagName.toLowerCase();
+        if (re.test(t) || tag == 'textarea') {
+            this.value = '';
+        }
+        else if (t == 'checkbox' || t == 'radio') {
+            this.checked = false;
+        }
+        else if (tag == 'select') {
+            this.selectedIndex = -1;
+        }
+        else if (t == "file") {
+            if (/MSIE/.test(navigator.userAgent)) {
+                $(this).replaceWith($(this).clone(true));
+            } else {
+                $(this).val('');
+            }
+        }
+        else if (includeHidden) {
+            // includeHidden can be the value true, or it can be a selector string
+            // indicating a special test; for example:
+            //  $('#myForm').clearForm('.special:hidden')
+            // the above would clean hidden inputs that have the class of 'special'
+            if ( (includeHidden === true && /hidden/.test(t)) ||
+                 (typeof includeHidden == 'string' && $(this).is(includeHidden)) ) {
+                this.value = '';
+            }
+        }
+    });
+};
+
+/**
+ * Resets the form data.  Causes all form elements to be reset to their original value.
+ */
+$.fn.resetForm = function() {
+    return this.each(function() {
+        // guard against an input with the name of 'reset'
+        // note that IE reports the reset function as an 'object'
+        if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType)) {
+            this.reset();
+        }
+    });
+};
+
+/**
+ * Enables or disables any matching elements.
+ */
+$.fn.enable = function(b) {
+    if (b === undefined) {
+        b = true;
+    }
+    return this.each(function() {
+        this.disabled = !b;
+    });
+};
+
+/**
+ * Checks/unchecks any matching checkboxes or radio buttons and
+ * selects/deselects and matching option elements.
+ */
+$.fn.selected = function(select) {
+    if (select === undefined) {
+        select = true;
+    }
+    return this.each(function() {
+        var t = this.type;
+        if (t == 'checkbox' || t == 'radio') {
+            this.checked = select;
+        }
+        else if (this.tagName.toLowerCase() == 'option') {
+            var $sel = $(this).parent('select');
+            if (select && $sel[0] && $sel[0].type == 'select-one') {
+                // deselect all other options
+                $sel.find('option').selected(false);
+            }
+            this.selected = select;
+        }
+    });
+};
+
+// expose debug var
+$.fn.ajaxSubmit.debug = false;
+
+// helper fn for console logging
+function log() {
+    if (!$.fn.ajaxSubmit.debug) {
+        return;
+    }
+    var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,'');
+    if (window.console && window.console.log) {
+        window.console.log(msg);
+    }
+    else if (window.opera && window.opera.postError) {
+        window.opera.postError(msg);
+    }
+}
+
+}));
diff --git a/src/main/webapp/scripts/project/projResourceMgmt.js b/src/main/webapp/scripts/project/projResourceMgmt.js
index 089c93c2ffce44551010b50731ce4cb1f0f323d3..6eca65edc30d360c9f42413a3621180dc749c978 100644
--- a/src/main/webapp/scripts/project/projResourceMgmt.js
+++ b/src/main/webapp/scripts/project/projResourceMgmt.js
@@ -24,6 +24,9 @@ XNAT.app.pResources={
 		var temp_html="<div class='colA'><div class='info simple'>What resource are you requiring?</div>" +
 				"<div class='row'><div class='rowTitle' for='pResource.name'>Title</div> <input class='pResourceField' required='true' data-required-msg='<b>Title</b> field is required.' data-prop-name='name' type='text' id='pResource.name' value='' placeholder='Natural Language Title'/></div>" +
 				"<div class='row'><div class='rowTitle' for='pResource.desc'>Description (optional)</div> <textarea class='pResourceField' data-prop-name='description' id='pResource.desc' placeholder='' /></div>" +
+				"<div class='row script-select-row'><div class='rowTitle'>Script to run</div> <select id='pScriptSelect'>" + 
+				'<option value="">NONE</option>' +
+				"</select></div>" +
 				"</div>";
 		temp_html+="<div class='colB'><div class='info simple'>Where will it be stored?</div>";
 		if(level=="proj"){
@@ -77,6 +80,24 @@ XNAT.app.pResources={
 		$("#pResource_form").html(temp_html);
 		$("#pResource_form").show();
 		$("#pResource_exist").height(430-$("#pResource_form").height());
+		var automationScriptsAjax = $.ajax({
+			type : "GET",
+			url:serverRoot+"/data/automation/scripts?format=json&XNAT_CSRF=" + window.csrfToken,
+			cache: false,
+			async: true,
+			context: this,
+			dataType: 'json'
+		});
+		automationScriptsAjax.done( function( data, textStatus, jqXHR ) {
+			if (typeof data.ResultSet !== "undefined" && typeof data.ResultSet.Result !== "undefined") {
+				XNAT.app.pResources.scripts = data.ResultSet.Result;
+				for (var i=0;i<XNAT.app.pResources.scripts.length;i++) {
+					$("#pScriptSelect").append('<option value="' + XNAT.app.pResources.scripts[i]["Script ID"] +
+					 '">' + XNAT.app.pResources.scripts[i]["Script ID"] + '</option>'); 
+				}
+			}
+		});
+
 	},
 	add:function(){		
 		var valid=true;
@@ -128,8 +149,102 @@ XNAT.app.pResources={
 		
 		if(valid){
 			this.configs.push(props);
-
 			this.save();
+			var scriptToRun = $("#pScriptSelect").find(":selected").text();
+			for (var i=0;i<XNAT.app.pResources.scripts.length;i++) {
+				if (scriptToRun == XNAT.app.pResources.scripts[i]["Script ID"]) {
+					var eventData = { event: ("Uploaded " + props.name),
+					      			scriptId: scriptToRun,
+					      			description: "Run " + scriptToRun + " upon " + props.name + " upload." };
+					var eventHandlerAjax = $.ajax({
+						type : "PUT",
+						url:serverRoot+"/data/projects/" + this.id + "/automation/handlers?XNAT_CSRF=" + window.csrfToken,
+						cache: false,
+						async: true,
+						context: this,
+						data: JSON.stringify(eventData),
+						contentType: "application/json; charset=utf-8"
+					});
+					eventHandlerAjax.done( function( data, textStatus, jqXHR ) {
+						console.log("NOTE:  Event handler added for " + props.name + " upload");
+						// Configure uploader
+						var getUploadConfigAjax = $.ajax({
+							type : "GET",
+							url:serverRoot+"/data/projects/" + this.id + "/config/automation_uploader/configuration?contents=true&XNAT_CSRF=" + window.csrfToken,
+							cache: false,
+							async: true,
+							context: this,
+							dataType: 'json'
+						});
+						getUploadConfigAjax.done( function( data, textStatus, jqXHR ) {
+							if (typeof(data)!=undefined && $.isArray(data) && data.length>0) {
+								var uploaderConfig = data; 
+								var alreadyConfig = false;
+								var NEW_HANDLER = "Uploaded " + props.name;
+								var thisConfig;
+								var dataPos;
+								for (j=0; j<uploaderConfig.length; j++) {
+									var currConfig = uploaderConfig[j];
+									if (currConfig.event==NEW_HANDLER && currConfig.scope=="prj") {
+										thisConfig = currConfig;
+										dataPos = j;
+										// NOTE:  We expect that the uploader configuration will have been created when the handler was
+										// created, so we'll modify it.
+										alreadyConfig = true;
+									}
+								}
+								var newHandlerObj = {
+									event:NEW_HANDLER,
+									eventScope:"prj",
+									launchFromResourceUploads:true,
+									launchFromCacheUploads:false,
+									launchWithoutUploads:false,
+									contexts:[props.type],
+									resourceConfigs:[props.name]
+								};
+								var hasGlobalUploaderConfig = (typeof XNAT.app.abu.uploaderConfig !== 'undefined' && XNAT.app.abu.uploaderConfig.constructor === Array);
+								if (alreadyConfig) {
+									thisConfig = newHandlerObj;
+									uploaderConfig[dataPos]=thisConfig;
+									if (hasGlobalUploaderConfig) {
+										for (var i=0;i<XNAT.app.abu.uploaderConfig.length;i++) {
+											if (XNAT.app.abu.uploaderConfig[i].eventScope ==" prj" && XNAT.app.abu.uploaderConfig[i].event==NEW_HANDLER) {
+												XNAT.app.abu.uploaderConfig[i]=newHandlerObj;
+											}
+										}
+									}
+								} else {
+									uploaderConfig.push(newHandlerObj);
+									if (hasGlobalUploaderConfig) {
+										XNAT.app.abu.uploaderConfig.push(newHandlerObj);
+									}
+								}
+								// Configure uploader
+								var putUploadConfigAjax = $.ajax({
+									type : "PUT",
+									url:serverRoot+"/data/projects/" + this.id + "/config/automation_uploader/configuration?inbody=true&XNAT_CSRF=" + window.csrfToken,
+									cache: false,
+									async: true,
+									context: this,
+									data: JSON.stringify(uploaderConfig),
+									contentType: "application/text; charset=utf-8"
+								});
+								putUploadConfigAjax.done( function( data, textStatus, jqXHR ) {
+									// Do nothing for now.
+								});
+								putUploadConfigAjax.fail( function( data, textStatus, jqXHR ) {
+        	 							xModalMessage("ERROR","The event handler to launch the automation script for this resource configuration " + 
+											"could not be added.  It must be added manually.");
+								});
+							}
+						});
+					});
+					eventHandlerAjax.fail( function( data, textStatus, jqXHR ) {
+						console.log("ERROR:  Event handler could not be added for " + props.name + " upload");
+					});
+					break;
+				}
+			}
 		}
 	},
 	save:function(){
@@ -140,6 +255,8 @@ XNAT.app.pResources={
         	{
         		closeModalPanel('saveResource');
         		XNAT.app.pResources.load();
+	                // Trigger automation uploader to reload handlers
+			XNAT.app.abu.loadResourceConfigs(); 
         	},
         	 failure: function(){
         	 	closeModalPanel('saveResource');
@@ -167,9 +284,28 @@ XNAT.app.pResources={
 			
 			this.configs=YAHOO.lang.JSON.parse(script);
 		}
-	    this.render();
+		//
+		// Load event handlers for finding event handlers linked to this project resource
+		//
+		var automationHandlerAjax = $.ajax({
+			type : "GET",
+			url:serverRoot+"/data/projects/" + this.id +  "/automation/handlers?format=json&XNAT_CSRF=" + window.csrfToken,
+			cache: false,
+			async: true,
+			context: this,
+			dataType: 'json'
+		});
+		automationHandlerAjax.done( function( data, textStatus, jqXHR ) {
+			if (typeof data.ResultSet !== "undefined" && typeof data.ResultSet.Result !== "undefined") {
+				this.eventHandlers = data.ResultSet.Result;
+			}
+	    		this.render(this.eventHandlers);
+		});
+		automationHandlerAjax.fail( function( data, textStatus, jqXHR ) {
+	    		this.render();
+		});
 	},
-	render:function(){
+	render:function(eventHandlers){
 		//identify columns
 		if(this.configs!=undefined && this.configs.length>0){
 			var tmpHtml="<dl class='header'><dl><dd class='col1'>&nbsp;</dd><dd class='colL col2'>Type</dd><dd class='colM col3'>Name</dd><dd class='colM col4'>Label</dd><dd class='colL col5'>Sub-directory</dd><dd class='colM col6'>Overwrite?</dd><dd class='colS col7'>Options</dd></dl></dl>	";
@@ -189,10 +325,18 @@ XNAT.app.pResources={
 				}
 				tmpHtml+="</dd>";
 				if(v1.description){
-					tmpHtml+="<dd class='colX'><b>Description:</b> "+v1.description +"</dd></dl>";
-				}else{
-					tmpHtml+="</dl>";
+					tmpHtml+="<dd class='colX'><b>Description:</b> "+v1.description +"</dd>";
+				}
+				if (typeof eventHandlers !== "undefined") {
+					for (var j=0;j<eventHandlers.length;j++) {
+						if (("Uploaded " + v1.name) == eventHandlers[j].event) {
+							tmpHtml+="<dd class='colX'><b>Script to run upon upload completion:</b> "+ eventHandlers[j].scriptId +"</dd>";
+							break;
+			
+						}
+					} 
 				}
+				tmpHtml+="</dl>";
 			});
 		}else{
 			var tmpHtml="<div style='color:grey;font-style:italic;'>None</div>";
@@ -210,4 +354,4 @@ XNAT.app.pResources.settingsDialog.cfg.queueProperty("buttons", [
 	   	XNAT.app.pResources.reset();
 	   	XNAT.app.pResources.settingsDialog.hide();
    }},isDefault:true}
-   ]);
\ No newline at end of file
+   ]);
diff --git a/src/main/webapp/scripts/uploaders/AutomationBasedUploader.js b/src/main/webapp/scripts/uploaders/AutomationBasedUploader.js
new file mode 100644
index 0000000000000000000000000000000000000000..f8ea5c9d900aa88cf717c705be2ccffa98cb9812
--- /dev/null
+++ b/src/main/webapp/scripts/uploaders/AutomationBasedUploader.js
@@ -0,0 +1,1270 @@
+//Copyright 2015 Washington University
+//Author: Mike Hodge <hodgem@mir.wustl.edu>
+
+/* 
+ * resource dialog is used to upload resources at any level
+ */
+
+if(typeof XNAT.app.abu === 'undefined'){
+	XNAT.app.abu={ cacheConstant:'_CACHE_',importHandler:'automation' }
+}
+
+if(typeof XNAT.app.abu.abuConfigs === 'undefined'){
+	XNAT.app.abu.abuConfigs={
+		load:function(){
+			XNAT.app.abu.loadResourceConfigs();
+			$('.uploadLink').click(function(e){ 
+				XNAT.app.abu.currentLink = e.target;
+	 		});
+		},
+		modalOpts:{
+			width: 840,  
+			height: 580,  
+			id: 'xmodal-abu',  
+			title: "Automation-Based Launcher/Uploader",
+			content: "<div id='modalUploadDiv'></div>",
+			ok: 'hide',
+			okLabel: 'Done',
+			okAction: function(){ },
+			cancel: 'hide',
+			cancelLabel: 'Cancel',
+			closeBtn: 'hide'
+		},
+
+		// Much of the remainder of the options code originates from ConfiguredResourceUploader.js
+		showUploadLink:function(){
+			$("a.abuLink").css("display","block");
+			$("a.abuLink").each(function(value){
+				$(this).click(function(){
+					XNAT.app.abu.initializeAbuUploader("upload");
+					return false;
+				});
+			});
+		},
+		showLaunchLink:function(){
+			$("a.ablLink").each(function(value){
+				$(this).click(function(){
+					XNAT.app.abu.initializeAbuUploader("launch");
+					return false;
+				});
+			});
+		},
+		hideUploadLink:function(){
+			$("a.abuLink").css("display","none");
+		},
+		hideLaunchLink:function(){
+			$("a.ablLink").css("display","none");
+		},
+		hideLinks:function(){
+			this.hideUploadLink();
+			this.hideLaunchLink();
+		},
+		getConfigsByType:function(type){
+			var configs=XNAT.app.abu.allResourceConfigs;
+			var temp=new Array();
+			jq.each(configs,function(i1,v1){
+				if(v1.type==type){
+					temp.push(v1);
+				}
+			});
+			return temp;
+		},
+		getAllConfigsByType:function(type, props){
+			var tmpConfigs=this.getConfigsByType(type);
+			var typeInfo=XNAT.data.context;
+			if(typeof typeInfo !== 'undefined' && !(typeof type !== 'undefined' && type.indexOf("ScanData")>=0)){
+				if(typeInfo.isSubjectAssessor){
+					tmpConfigs=tmpConfigs.concat(this.getConfigsByType("xnat:subjectAssessorData"));
+				}
+				if(typeInfo.isImageAssessor){
+					tmpConfigs=tmpConfigs.concat(this.getConfigsByType("xnat:imageAssessorData"));
+				}
+				if(typeInfo.isImageSession){
+					tmpConfigs=tmpConfigs.concat(this.getConfigsByType("xnat:imageSessionData"));
+				}
+				if(typeInfo.isImageScan){
+					tmpConfigs=tmpConfigs.concat(this.getConfigsByType("xnat:imageScanData"));
+				}
+				if(typeInfo.isSubject){
+					tmpConfigs=tmpConfigs.concat(this.getConfigsByType("xnat:subjectData"));
+				}
+			}
+			
+			var tempConfigs2=new Array();
+			//allow filtering of links
+			jq.each(tmpConfigs,function(i1,v1){
+				if(typeof props!=='undefined' && v1.filter){
+					var filters=v1.filter.split(",");
+					var matched=false;
+					jq.each(filters,function (i2,v2){
+						if(!matched){
+							if((v2.trim()==props.trim())){
+								matched=true;
+							}
+						}
+					});
+					if(matched){
+						tempConfigs2.push(v1);
+					}
+				}else{
+					tempConfigs2.push(v1);
+				}
+			});
+			return tempConfigs2;
+		}
+
+
+	}
+}
+
+XNAT.app.abu.showScanLinks = function(){
+			$('.uploadLink[data-type*="ScanData"]').each(function(value){
+				var type=$(this).attr('data-type');
+				var tempConfigs=new Array();
+	
+				var props=$(this).attr('data-props');
+				
+				var tempConfigs=XNAT.app.abu.abuConfigs.getAllConfigsByType(type,props)			
+				if(tempConfigs.length>0 || XNAT.app.abu.hasContextEvents("Upload",type)){
+					if(value.dontHide){
+						$(value).color(value.defaultColor);
+						$(value).css('cursor:pointer');
+					}
+					
+					$(this).click(function(){
+						XNAT.app.abu.initializeAbuUploader("upload");
+						return false;
+					});
+					$(this).show();
+				}else{
+					if(!value.dontHide){
+						$(this).hide();
+					}
+				}
+			});
+}
+
+XNAT.app.abu.loadResourceConfigs = function(){
+
+	var resourceConfigAjax = $.ajax({
+		type : "GET",
+		url:serverRoot+'/data/projects/' + XNAT.data.context.project +'/config/resource_config/script?contents=true&format=json',
+		cache: false,
+		async: true,
+		context: this,
+		dataType: 'json'
+	 });
+	resourceConfigAjax.done( function( data, textStatus, jqXHR ) {
+
+		XNAT.app.abu.allResourceConfigs = data;
+		XNAT.app.abu.getAutomationHandlers();
+
+	});
+	resourceConfigAjax.fail( function( data, textStatus, error ) {
+
+		// Setting resourceConfigsObject to empty array.  This is usually hit because project has no resources configured.
+		XNAT.app.abu.allResourceConfigs = [];
+		XNAT.app.abu.getAutomationHandlers();
+
+	});
+
+}
+
+XNAT.app.abu.getAutomationHandlers = function(){
+
+	var initializeBuildAjax = $.ajax({
+		type : "GET",
+		url:serverRoot+"/data/projects/" + XNAT.data.context.projectID +  "/automation/handlers?format=json&XNAT_CSRF=" + window.csrfToken,
+		cache: false,
+		async: true,
+		context: this,
+		dataType: 'json'
+	});
+	initializeBuildAjax.done( function( data, textStatus, jqXHR ) {
+
+		if (!(typeof data.ResultSet !== 'undefined' && typeof data.ResultSet.Result !== 'undefined')) {
+			return;
+		}
+
+		XNAT.app.abu.automationEvents = data.ResultSet.Result;
+		var sitewideHandlerAjax = $.ajax({
+			type : "GET",
+	  		url:serverRoot+"/data/automation/handlers?format=json&XNAT_CSRF=" + window.csrfToken,
+			cache: false,
+			async: true,
+			context: this,
+			dataType: 'json'
+		});
+		sitewideHandlerAjax.done( function( data, textStatus, jqXHR ) {
+
+			if (typeof data.ResultSet !== 'undefined' && typeof data.ResultSet.Result !== 'undefined') {
+				XNAT.app.abu.automationEvents = XNAT.app.abu.automationEvents.concat(data.ResultSet.Result);
+			}
+			var events = XNAT.app.abu.automationEvents;
+			var resources = XNAT.app.abu.allResourceConfigs;
+			var type = $('.uploadLink').not('[data-type*="ScanData"]').attr("data-type");
+			var props = $('.uploadLink').not('[data-type*="ScanData"]').attr("data-props");
+			XNAT.app.abu.contextResourceConfigs = XNAT.app.abu.abuConfigs.getAllConfigsByType(type,props);		
+			// Determine whether or not to display links
+			if (events.length>0) {
+				XNAT.app.abu.initUploaderConfig();
+				if (XNAT.app.abu.hasContextEvents("Upload",type)) {
+					XNAT.app.abu.abuConfigs.showUploadLink();
+				} else {
+					XNAT.app.abu.abuConfigs.hideUploadLink();
+				}
+				if (XNAT.app.abu.hasContextEvents("Launch",type)) {
+					XNAT.app.abu.abuConfigs.showLaunchLink();
+				} else {
+					XNAT.app.abu.abuConfigs.hideLaunchLink();
+				}
+			} else {
+				XNAT.app.abu.uploaderConfig = [];
+				XNAT.app.abu.abuConfigs.hideLaunchLink();	
+				if (XNAT.app.abu.contextResourceConfigs.length>0) {
+					XNAT.app.abu.abuConfigs.showUploadLink();	
+				} else {
+					XNAT.app.abu.abuConfigs.hideUploadLink();	
+				}
+			}
+			XNAT.app.abu.showScanLinks();
+		});
+		sitewideHandlerAjax.fail( function( data, textStatus, jqXHR ) {
+			console.log("GetAutomationHandlers error -" + error); 
+			XNAT.app.abu.abuConfigs.hideLinks();	
+		});
+
+	});
+	initializeBuildAjax.fail( function( data, textStatus, jqXHR ) {
+		console.log("GetAutomationHandlers error -" + error); 
+		XNAT.app.abu.abuConfigs.hideLinks();	
+	});
+}
+
+XNAT.app.abu.hasContextEvents = function(usage,dataType){
+	var events = XNAT.app.abu.automationEvents;
+	var uploaderConfig = XNAT.app.abu.uploaderConfig;
+	if (events.length>0) {
+		for (var i=0; i<events.length; i++) {
+			var currEvent = events[i];
+			for (var j=0; j<uploaderConfig.length; j++) {
+				var currConfig = uploaderConfig[j];
+				if (currEvent.event==currConfig.event && currEvent.scope==currConfig.eventScope) {
+					if (usage == 'Launch' && currConfig.launchWithoutUploads && XNAT.app.abu.checkConfigContext(currConfig,dataType)) {
+						return true;
+					}
+					else if (usage == 'Upload' && (currConfig.launchFromResourceUploads || currConfig.launchFromCacheUploads) && XNAT.app.abu.checkConfigContext(currConfig,dataType)) {
+						return true;
+					}
+				}
+			}
+		}
+	} 
+	return false;
+}
+
+XNAT.app.abu.eventHandlerChange = function(){
+	var eventHandler = $('#eventHandlerSelect').val();
+	if (typeof eventHandler === 'undefined' || eventHandler == null || eventHandler.length<1 && ($("#handlerDefaultOption").html() == 'NONE DEFINED' || $("#handlerDefaultOption").html() == 'SELECT')) {
+		$("#abu-process-button").addClass("abu-button-disabled");
+	} else if ((XNAT.app.abu.usageSelect=='Launch') || (abu._fileUploader._uploadStarted && abu._fileUploader._filesInProgress<1)) {
+		$("#abu-process-button").removeClass("abu-button-disabled");
+	}
+}
+
+XNAT.app.abu.initUploaderConfig = function(){
+
+	var uploaderConfigAjax = $.ajax({
+		type : "GET",
+ 		url:serverRoot+'/data/projects/' + XNAT.data.context.project +'/config/automation_uploader/configuration?contents=true',
+		cache: false,
+		async: false,
+		context: this,
+		dataType: 'json'
+	 });
+	uploaderConfigAjax.done( function( data, textStatus, jqXHR ) {
+
+		if (typeof data !== 'undefined' && $.isArray(data)) {
+			XNAT.app.abu.uploaderConfig = data;
+		} else {
+			XNAT.app.abu.uploaderConfig = [];
+		}
+		var uploaderSiteConfigAjax = $.ajax({
+			type : "GET",
+	 		url:serverRoot+'/data/config/automation_uploader/configuration?contents=true',
+			cache: false,
+			async: false,
+			context: this,
+			dataType: 'json'
+		 });
+		uploaderSiteConfigAjax.done( function( data, textStatus, jqXHR ) {
+			if (typeof data !== 'undefined' && $.isArray(data) && data.length>0) {
+				Array.prototype.push.apply(XNAT.app.abu.uploaderConfig,data);
+			}
+		});
+		uploaderConfigAjax.fail( function( data, textStatus, error ) {
+			// Do nothing, for now
+		});
+	});
+
+	uploaderConfigAjax.fail( function( data, textStatus, error ) {
+		XNAT.app.abu.uploaderConfig = [];
+	});
+
+}
+
+XNAT.app.abu.checkConfigContext = function(currConfig,dataType) {
+	if (typeof(currConfig.contexts) === 'undefined' || currConfig.contexts.length<1) {
+		return true;
+	}
+	for (var i=0;i<currConfig.contexts.length;i++) {
+		if (typeof dataType !== 'undefined') {
+			if (currConfig.contexts[i]==dataType) {
+				return true;
+			}
+		} 
+		if (
+			(!(typeof dataType !== 'undefined' && dataType.indexOf("ScanData")>=0)) && 
+			((currConfig.contexts[i]==XNAT.data.context.xsiType) ||
+				(XNAT.data.context.isSubjectAssessor && currConfig.contexts[i]=="xnat:subjectAssessorData") || 
+				(XNAT.data.context.isImageAssessor && currConfig.contexts[i]=="xnat:imageAssessorData") || 
+				(XNAT.data.context.isImageSession && currConfig.contexts[i]=="xnat:imageSessionData") || 
+				(XNAT.data.context.isImageScan && currConfig.contexts[i]=="xnat:imageScanData") || 
+				(XNAT.data.context.isSubject && currConfig.contexts[i]=="xnat:subjectData"))) {
+			return true;
+		} 
+	}
+	return false;
+}
+
+XNAT.app.abu.populateEventHandlerSelect = function(){
+	var events = XNAT.app.abu.automationEvents;
+	var type = $(XNAT.app.abu.currentLink).attr("data-type");
+	var props = $(XNAT.app.abu.currentLink).attr("data-props");
+	XNAT.app.abu.contextResourceConfigs = XNAT.app.abu.abuConfigs.getAllConfigsByType(type,props);		
+	var resourceConfigs = XNAT.app.abu.contextResourceConfigs;
+	var uploaderConfig = XNAT.app.abu.uploaderConfig;
+	var usageSelect = $('#usageSelect').val();
+	var resourceSelect = $('#resourceSelect').val();
+	$('#eventHandlerSelect').find('option').remove();
+	if (events.length>0) {
+		$('#eventHandlerSelect').append('<option id="handlerDefaultOption" value="">' + 
+			((usageSelect=='Launch' || resourceSelect==XNAT.app.abu.cacheConstant) ?  'SELECT' : 'DEFAULT') + '</option>'); 
+		outerLoop:
+		for (var i=0; i<events.length; i++) {
+			var currEvent = events[i];
+			for (var j=0; j<uploaderConfig.length; j++) {
+				var currConfig = uploaderConfig[j];
+				if (currEvent.event==currConfig.event && currEvent.scope==currConfig.eventScope) {
+					var doAssign = true;
+					if (usageSelect == 'Upload' && resourceSelect==XNAT.app.abu.cacheConstant && !currConfig.launchFromCacheUploads) {
+						doAssign = false;
+					} else if (usageSelect == 'Launch' && !currConfig.launchWithoutUploads) {
+						doAssign = false;
+					} else if (usageSelect == 'Upload' && resourceSelect != XNAT.app.abu.cacheConstant) {
+						if (!currConfig.launchFromResourceUploads) {
+							doAssign = false;
+						} else if (currConfig.resourceConfigs.length>0 && $.inArray(resourceSelect,currConfig.resourceConfigs)<0) {
+							doAssign = false;
+						}
+					}
+					if (!XNAT.app.abu.checkConfigContext(currConfig,type)) {
+						doAssign = false;
+					}
+					if (doAssign) {
+						$('#eventHandlerSelect').append('<option value="' + currEvent.event + '" class="' + currEvent.scope + '">' +
+						 ((typeof(currEvent.description) !== 'undefined' && currEvent.description.length>0) ? currEvent.description : currEvent.scriptId) + '</option>');
+					}
+					continue outerLoop;
+				}
+			}
+			$('#eventHandlerSelect').append('<option value="' + currEvent.event + '" class="' + currEvent.scope + '">' +
+				 ((typeof(currEvent.description) !== 'undefined' && currEvent.description.length>0) ? currEvent.description : currEvent.scriptId) + '</option>');
+		}
+		if ($('#eventHandlerSelect').find('option').length==1) {
+			$('#handlerDefaultOption').html('NONE DEFINED'); 
+		} else if ($('#eventHandlerSelect').find('option').length==2) {
+			$('#eventHandlerSelect').find('option').get(0).remove();
+		}
+	} else {
+		$('#eventHandlerSelect').append('<option value="">NONE DEFINED</option>'); 
+	}
+}
+
+XNAT.app.abu.populateWhatToDoSelect = function(){
+	var events = XNAT.app.abu.automationEvents;
+	var type = $(XNAT.app.abu.currentLink).attr("data-type");
+	var props = $(XNAT.app.abu.currentLink).attr("data-props");
+	XNAT.app.abu.contextResourceConfigs = XNAT.app.abu.abuConfigs.getAllConfigsByType(type,props);		
+	var resourceConfigs = XNAT.app.abu.contextResourceConfigs;
+	var uploaderConfig = XNAT.app.abu.uploaderConfig;
+	var usageSelect = $('#usageSelect').val();
+	var resourceSelect = $('#resourceSelect').val();
+	if (XNAT.app.abu.contextResourceConfigs!=undefined && XNAT.app.abu.contextResourceConfigs.length>0) {
+		for (var h=0; h<resourceConfigs.length; h++) {
+			var resourceMatch = false;
+			if (events.length>0) {
+				outerLoop:
+				for (var i=0; i<events.length; i++) {
+					var currEvent = events[i];
+					if (currEvent.event == ("Uploaded " + resourceConfigs[h].name)) {
+						for (var j=0; j<uploaderConfig.length; j++) {
+							var currConfig = uploaderConfig[j];
+							if (currEvent.event==currConfig.event && currEvent.scope==currConfig.eventScope) {
+								var doAssign = true;
+								if ((usageSelect == 'Launch') ||
+								   (!(currConfig.launchFromResourceUploads)) ||
+								   (!XNAT.app.abu.checkConfigContext(currConfig,type))) {
+									doAssign = false;
+								}
+								if (doAssign) {
+									$('#whatToDoSelect').append('<option value="resource- ' + resourceConfigs[h].name + ':launch-' + currEvent.event + '" class="' + currEvent.scope + '">' + 
+				                                                ((typeof resourceConfigs[h].description !== 'undefined' && resourceConfigs[h].description.length>0) ? resourceConfigs[h].description : resourceConfigs[h].name) + " --> " + 
+									 ((typeof(currEvent.description) !== 'undefined' && currEvent.description.length>0) ? currEvent.description : currEvent.scriptId) + '</option>');
+									resourceMatch = true;
+								}
+								continue outerLoop;
+							}
+						}
+						$('#whatToDoSelect').append('<option value="resource-' + resourceConfigs[h].name + ':launch-' + currEvent.event + '" class="' + currEvent.scope + '">' + 
+				                                ((typeof resourceConfigs[h].description !== 'undefined' && resourceConfigs[h].description.length>0) ? resourceConfigs[h].description : resourceConfigs[h].name) + " --> " + 
+							 ((typeof(currEvent.description) !== 'undefined' && currEvent.description.length>0) ? currEvent.description : currEvent.scriptId) + '</option>');
+						resourceMatch = true;
+					}
+				}
+			} 
+			if (!resourceMatch) {
+				$("#whatToDoSelect").append('<option value="resource-' + resourceConfigs[h].name + '">' + ((typeof resourceConfigs[h].description !== 'undefined' && resourceConfigs[h].description.length>0) ? resourceConfigs[h].description : resourceConfigs[h].name) + '</option>');
+			}
+		}
+	} 
+	if (events.length>0) {
+		outerLoop:
+		for (var i=0; i<events.length; i++) {
+			var currEvent = events[i];
+			for (var j=0; j<uploaderConfig.length; j++) {
+				var currConfig = uploaderConfig[j];
+				if (currEvent.event==currConfig.event && currEvent.scope==currConfig.eventScope) {
+					var doAssign = true;
+					if ((usageSelect == 'Launch') ||
+					   (!(currConfig.launchFromCacheUploads)) ||
+					   (!XNAT.app.abu.checkConfigContext(currConfig,type))) {
+						doAssign = false;
+					}
+					if (doAssign) {
+						$('#whatToDoSelect').append('<option value="resource-' + XNAT.app.abu.cacheConstant + ':launch-' + currEvent.event + '" class="' + currEvent.scope + '">' + "Upload --> " + 
+						 ((typeof(currEvent.description) !== 'undefined' && currEvent.description.length>0) ? currEvent.description : currEvent.scriptId) + '</option>');
+					}
+					continue outerLoop;
+				}
+			}
+			$('#whatToDoSelect').append('<option value="resource-' + XNAT.app.abu.cacheConstant + ':launch-' + currEvent.event + '" class="' + currEvent.scope + '">' + "Upload --> " + 
+				 ((typeof(currEvent.description) !== 'undefined' && currEvent.description.length>0) ? currEvent.description : currEvent.scriptId) + '</option>');
+		}
+	} 
+	XNAT.app.abu.whatToDoChange();
+}
+
+XNAT.app.abu.initializeAbuUploader = function(usageType){
+
+	if (usageType === "upload") {
+		XNAT.app.abu.abuConfigs.modalOpts.title = "Upload Additional Files";
+	} else if (usageType === "launch") {
+		XNAT.app.abu.abuConfigs.modalOpts.title = "Script Launcher";
+	}
+	$("div.title").find("span.inner").html(XNAT.app.abu.abuConfigs.modalOpts.title);
+	var events = XNAT.app.abu.automationEvents;
+	var type = $(XNAT.app.abu.currentLink).attr("data-type");
+	var props = $(XNAT.app.abu.currentLink).attr("data-props");
+	XNAT.app.abu.contextResourceConfigs = XNAT.app.abu.abuConfigs.getAllConfigsByType(type,props);		
+	var resourceConfigs = XNAT.app.abu.contextResourceConfigs;
+	var scriptDiv = "<div class='abu-xnat-interactivity-area-contents'>";
+	var usageSelect = '<div class="abu-xnat-interactivity-area-sub usage-area"><span class="interactivityAreaSpan">Usage:</span> <select id="usageSelect" onchange="XNAT.app.abu.usageSelectAction()">'; 
+                if (typeof usageType == 'undefined' || usageType == 'upload') {
+			XNAT.app.abu.usageSelect = 'Upload';
+			usageSelect+='<option value="Upload">Upload Files</option>'; 
+		}
+                if (typeof usageType == 'undefined' || usageType == 'launch') {
+			if (usageType == 'launch') {
+				XNAT.app.abu.usageSelect = 'Launch';
+			}
+			usageSelect+='<option value="Launch">Script Launcher</option>'; 
+		}
+	usageSelect+='</select></div>';
+	var resourceSelect = '<div class="abu-xnat-interactivity-area-sub upload-area"><span class="interactivityAreaSpan">';
+	if (usageType === "upload") {
+		resourceSelect+='Configured Resource:</span>';
+	} else {
+		resourceSelect+='Upload location:</span>';
+	}
+        resourceSelect+='<select id="resourceSelect" onchange="XNAT.app.abu.updateModalAction()">'; 
+	if (XNAT.app.abu.contextResourceConfigs!=undefined && XNAT.app.abu.contextResourceConfigs.length>0) {
+		resourceSelect+='<option value="' + XNAT.app.abu.cacheConstant + '">Cache Space</option>'; 
+		for (var i=0; i<resourceConfigs.length; i++) {
+			resourceSelect+=('<option value="' + resourceConfigs[i].name + '">' + resourceConfigs[i].name + '</option>');
+		}
+	} else {
+		resourceSelect+='<option value="' + XNAT.app.abu.cacheConstant + '">Cache Space</option>'; 
+	}
+	resourceSelect+='</select></div>';
+	var eventSelect = '<div class="abu-xnat-interactivity-area-sub eventhandler-area"><span id="script-select-text" class="interactivityAreaSpan">Script to run:</span> <select id="eventHandlerSelect" onchange="XNAT.app.abu.eventHandlerChange()">'; 
+	eventSelect+='</select></div>';
+	var whatToDoSelect = '<div class="abu-xnat-interactivity-area-sub whattodo-area"><span id="script-select-text" class="interactivityAreaSpan">Upload Selector:</span> <select id="whatToDoSelect" onchange="XNAT.app.abu.whatToDoChange()">'; 
+	whatToDoSelect+='</select></div>';
+	scriptDiv+=usageSelect;
+	scriptDiv+=resourceSelect;
+	scriptDiv+=eventSelect;
+	scriptDiv+=whatToDoSelect;
+	scriptDiv+='</div>';
+	try {
+		xModalOpenNew(XNAT.app.abu.abuConfigs.modalOpts);
+		abu.initializeUploader({
+			element:document.getElementById('modalUploadDiv'),
+			action:'TBD',
+			debug:true,
+			doneFunction:function(){
+					// Since we're using the update-stats=false parameter for resource uploads, we need to call catalog refresh when we're finished uploading.
+					XNAT.app.abu.updateResourceStats();
+					XNAT.app.abu.sendWorkflowWhenDone();
+					xModalCloseNew(XNAT.app.abu.abuConfigs.modalOpts.id);
+				},
+			uploadStartedFunction:function(){
+					if (abu._fileUploader._currentAction.indexOf("import-handler=" + XNAT.app.abu.importHandler)>=0 && (typeof(XNAT.app.abu.buildPath) == 'undefined' || XNAT.app.abu.buildPath == '')) {
+						XNAT.app.abu.initializeBuildPath();
+					} 
+					// Force press of processing button after upload is started so workflow is created.  Only want one workflow per upload.
+					// Don't want cancellation of process during upload or incomplete file could end up in resource
+					if (abu._fileUploader._currentAction.indexOf("import-handler=" + XNAT.app.abu.importHandler)<0) {
+						$("#abu-done-button").addClass("abu-button-disabled");
+					}
+					$("#resourceSelect").prop('disabled','disabled');
+					$("#whatToDoSelect").prop('disabled','disabled');
+					$("#usageSelect").prop('disabled','disabled');
+				},
+			uploadCompletedFunction:function(){
+					var eventHandler = $('#eventHandlerSelect').val();
+					if (typeof eventHandler !== 'undefined' && eventHandler != null && eventHandler.length>0) {
+						$("#abu-process-button").removeClass("abu-button-disabled");
+					} else {
+						$("#abu-done-button-text").html("Done");
+						$("#abu-done-button").removeClass("abu-button-disabled");
+					}
+				},
+			processFunction:function(){
+					XNAT.app.abu.processFiles();
+				},
+			showEmailOption:true,
+			showExtractOption:(usageType !== 'launch'),
+			showVerboseOption:false,
+			showUpdateOption:false,
+		});	
+		abu._fileUploader.buildUploaderDiv();
+		$(".abu-xnat-interactivity-area").html(scriptDiv);
+		XNAT.app.abu.populateEventHandlerSelect();
+		abu._fileUploader._currentAction = XNAT.app.abu.automationHandlerUrl(true);
+		if (typeof usageType !== 'undefined' && usageType != null) {
+			$(".usage-area").css("display","none");
+			if (usageType === 'launch') {
+				var eventHandler = $('#eventHandlerSelect').val();
+				if (eventHandler != undefined && eventHandler != null && eventHandler.length>0) {
+					$("#abu-process-button").removeClass("abu-button-disabled");
+				} else {
+					$("#abu-done-button").removeClass("abu-button-disabled");
+					$("#abu-done-button-text").html("Done");
+				}
+				$(".upload-area").css("display","none");
+				$(".whattodo-area").css("display","none");
+				$("#abu-upload-button").addClass("abu-button-disabled");
+				$("#abu-upload-button").css("display","none");
+				$("#abu-process-button-text").html("Run script");
+				$("#file-uploader-instructions-sel").css("display","none");
+			} else {
+				XNAT.app.abu.populateWhatToDoSelect();
+				$("#abu-process-button").addClass("abu-button-disabled");
+				$(".upload-area").css("display","none");
+				$(".eventhandler-area").css("display","none");
+			}
+		} else {
+			$(".whattodo-area").css("display","none");
+		}
+	} catch(e) {
+		console.log(e.stack);
+		xModalMessage('Error','ERROR:  Could not parse event handlers');
+	}
+
+}
+
+XNAT.app.abu.automationHandlerUrl = function(withBuildPath){
+	return serverRoot+"/REST/services/import?import-handler=".replace(/\/\//,"/") + XNAT.app.abu.importHandler + ((withBuildPath) ? "&process=false&buildPath=" + XNAT.app.abu.buildPath : '') + "&XNAT_CSRF=" + window.csrfToken;
+}
+
+XNAT.app.abu.usageSelectAction = function(){
+	XNAT.app.abu.usageSelect = $('#usageSelect').val();
+	if (XNAT.app.abu.usageSelect=='Upload') {
+		XNAT.app.abu.populateEventHandlerSelect();
+		$("#abu-done-button").removeClass("abu-button-disabled");
+		$("#abu-upload-button").removeClass("abu-button-disabled");
+		$("#abu-process-button").addClass("abu-button-disabled");
+		$("#script-select-text").html("Post-upload processing script:");
+		$("#abu-process-button-text").html("Process files");
+		$("#resourceSelect").prop('disabled',false);
+		$(".response_text").html('');
+	} else if (XNAT.app.abu.usageSelect=='Launch') { 
+		XNAT.app.abu.populateEventHandlerSelect();
+		$("#abu-done-button").removeClass("abu-button-disabled");
+		$("#abu-upload-button").addClass("abu-button-disabled");
+		var eventHandler = $('#eventHandlerSelect').val();
+		if (eventHandler != undefined && eventHandler != null && eventHandler.length>0) {
+			$("#abu-process-button").removeClass("abu-button-disabled");
+		}
+		$("#script-select-text").html("Script to launch:");
+		$("#abu-process-button-text").html("Run script");
+		$("#resourceSelect").prop('disabled','disabled');
+		$(".response_text").html('');
+	}
+}
+
+XNAT.app.abu.updateModalAction = function(){
+	XNAT.app.abu.configuredResource = $('#resourceSelect').val();
+	if (XNAT.app.abu.configuredResource==XNAT.app.abu.cacheConstant) {
+		abu._fileUploader._currentAction = XNAT.app.abu.automationHandlerUrl();
+		XNAT.app.abu.populateEventHandlerSelect();
+		return;
+	} else {
+		var resourceConfigs = XNAT.app.abu.contextResourceConfigs;
+		if (XNAT.app.abu.contextResourceConfigs!=undefined && XNAT.app.abu.contextResourceConfigs!=null && XNAT.app.abu.contextResourceConfigs.length>0) {
+			for (var i=0; i<resourceConfigs.length; i++) {
+				if (XNAT.app.abu.configuredResource==resourceConfigs[i].name) {
+					// NOTE:  Setting update-stats=false (no workflow entry for individual files).  The process step will create a workflow entry for the entire upload.
+					abu._fileUploader._currentAction = $(XNAT.app.abu.currentLink).attr("data-uri") + "/resources/" + resourceConfigs[i].label + "/files" + resourceConfigs[i].subdir + "?overwrite=" + resourceConfigs[i].overwrite + "&update-stats=false&XNAT_CSRF=" + window.csrfToken;
+					XNAT.app.abu.populateEventHandlerSelect();
+					return;
+				}
+			}
+		}
+	}
+	XNAT.app.abu.populateEventHandlerSelect();
+}
+
+XNAT.app.abu.whatToDoChange = function(){
+	XNAT.app.abu.whatToDo = $('#whatToDoSelect').val();
+        var whatToDo = XNAT.app.abu.whatToDo;
+	var resourceSelect = whatToDo.replace(/^resource-/,"").replace(/:launch-.*$/,"").trim();
+	var launchSelect = ((whatToDo.indexOf(":launch-")>=0) ? whatToDo.replace(/^.*:launch-/,"") : "").trim();
+	$('#resourceSelect').val(resourceSelect);
+	XNAT.app.abu.updateModalAction();
+	$('#eventHandlerSelect').val(launchSelect);
+}
+
+XNAT.app.abu.initializeBuildPath = function(){
+		// NOTE:  This call is made synchronously so we only create a build path if we do a cached upload
+		var initializeBuildAjax = $.ajax({
+			type : "POST",
+	  		url:XNAT.app.abu.automationHandlerUrl(false),
+			cache: false,
+			async: false,
+			context: this,
+			dataType: 'text',
+			success:function(data) {
+				XNAT.app.abu.buildPath=XNAT.app.abu.processReturnedText(data,false);
+				abu._fileUploader._currentAction = XNAT.app.abu.automationHandlerUrl(true);
+			}
+		  });
+}
+
+XNAT.app.abu.validatePassedParameters=function() {
+	var errorText = '';
+	var paramTextEle = $(".passParamText");
+	var paramData = {};
+	for (var i=0;i<paramTextEle.length;i++) {
+		var paramText = $($(paramTextEle).get(i)).html();
+		for (var j=0;j<this.paramsToPass.length;j++) {
+			if (this.paramsToPass[j].name==paramText) {
+				var paramVal = $($(".passParamInput").get(i)).val();
+				paramData[paramText] = paramVal;
+				var paramRequired = (this.paramsToPass[j].required==undefined || this.paramsToPass[j].required.toString().toLowerCase() != 'false') ? true : false;
+				if (paramRequired && (typeof(paramVal)=='undefined' || paramVal.length<1)) {
+					errorText+="ERROR:  " + paramText + " is required;  ";
+				}
+				if (this.paramsToPass[j].type == undefined) { 
+					return;
+				}
+				if (this.paramsToPass[j].type.toString().toLowerCase() == "float" && isNaN(paramVal)) {
+					errorText+="ERROR:  " + paramText + " must be a valid floating point number;  ";
+				} else if (this.paramsToPass[j].type.toString().toLowerCase() == "integer" && (isNaN(paramVal) || paramVal % 1 !== 0)) {
+					errorText+="ERROR:  " + paramText + " must be a valid integer;  ";
+				}
+				break;
+			}
+		}
+	}
+	if (errorText.length>0) {
+		$("#passParamErrorDiv").html(errorText);
+		return false;
+	}
+	XNAT.app.abu.paramData = paramData;
+	return true;
+}
+
+XNAT.app.abu.updateResourceStats=function() {
+	if (XNAT.app.abu.usageSelect !== 'Launch') {
+		if (abu._fileUploader._currentAction.indexOf("import-handler=" + XNAT.app.abu.importHandler)<0) {
+			var updateStatsUrl = "/data/services/refresh/catalog?resource=" + 
+				abu._fileUploader._currentAction.replace(/\/files[?].*$/i,'').replace(/^\/data\//i,"/archive/").replace(/^\/REST\//i,"/archive/" +
+				"&options=populateStats");
+			var updateStatsAjax = $.ajax({
+				type : "POST",
+				url:serverRoot+updateStatsUrl,
+				cache: false,
+				async: true,
+				context: this,
+				dataType: 'text'
+			  });
+			updateStatsAjax.done( function( data, textStatus, jqXHR ) {
+				// Do nothing.
+			});
+			updateStatsAjax.fail( function( data, textStatus, jqXHR ) {
+				// Do nothing.
+			});
+		}
+	}
+}
+
+XNAT.app.abu.sendWorkflowWhenDone=function() {
+	if (XNAT.app.abu.usageSelect !== 'Launch') {
+		if (abu._fileUploader._currentAction.indexOf("import-handler=" + XNAT.app.abu.importHandler)<0) {
+			// TO DO:  Implement this
+		}
+	}
+}
+
+XNAT.app.abu.processFiles=function() {
+
+		$(".abu-files-processing").css("display","inline");
+
+		// Since we're using the update-stats=false parameter, we need to call catalog refresh when we're finished uploading.
+		XNAT.app.abu.updateResourceStats();
+
+		var eventHandler = $('#eventHandlerSelect').val();
+		var eventHandlerElement = $('#eventHandlerSelect').find('option:selected').get(0);
+ 		if (typeof(eventHandlerElement) !== 'undefined' && eventHandlerElement != null) {
+			var eventHandlerScope = eventHandlerElement.className;
+		}
+
+		for (var i=0;i<this.uploaderConfig.length;i++) {
+			if (this.uploaderConfig[i].event==eventHandler && this.uploaderConfig[i].eventScope==eventHandlerScope &&
+			 	(this.uploaderConfig[i].parameters!=undefined && this.uploaderConfig[i].parameters!=null && this.uploaderConfig[i].parameters.length>0)) {
+				this.paramsToPass = this.uploaderConfig[i].parameters;
+				break;
+			}
+		}
+
+		if (typeof(this.paramsToPass) !== 'undefined' && this.paramsToPass != null && this.paramsToPass.length>0) {
+
+			var pModalOpts = {
+				width: 740,  
+				height: 480,  
+				id: 'xmodal-passed-param',  
+				title: "Information required",
+				content: "<div id='modalParamsToPassDiv'></div>",
+				ok: 'show',
+				okLabel: 'Continue',
+				okAction: function(modl){ 
+							if (XNAT.app.abu.validatePassedParameters()) {
+								XNAT.app.abu.continueProcessing();
+								modl.close();
+							} else {
+								$("#passParamErrorDiv").removeClass("hidden");
+							}
+	
+						 },
+				okClose: false,
+				cancel: 'Cancel',
+				cancelLabel: 'Cancel',
+				cancelAction: function(){ xModalCloseNew(XNAT.app.abu.abuConfigs.modalOpts.id); },
+				closeBtn: 'hide'
+			};
+			xModalOpenNew(pModalOpts);
+			paramText='';
+			for (var i=0;i<this.paramsToPass.length;i++) {
+				paramText+='<tr><td style="padding-bottom:5px"><span id="passParamText' + i + '" class="passParamText" style="font-weight:bold">' + this.paramsToPass[i].name + 
+						'</span></td><td style="padding-bottom:5px"><input type="text" size="30" id="passParamInput' + i + '" class="passParamInput"></td></tr>';
+			}
+			$('#modalParamsToPassDiv').html('<div id="passParamErrorDiv" class="error hidden"></div><h3>' +
+				 ((this.paramsToPass.length>0) ? "Please supply values for the following parameters:" :  "Please supply a value for the following parameter:") +
+'				</h3><div style="width:100px"><table>' + paramText + "</table>");
+			// Not sure why the setTimeout seems necessary.
+			$(".passParamInput").get(0).focus();
+			setTimeout(function(){
+				$(".passParamInput").get(0).focus();
+			},100);
+
+		} else {
+			XNAT.app.abu.paramData = {};
+			XNAT.app.abu.continueProcessing();
+		}
+}
+
+XNAT.app.abu.continueProcessing=function() {
+
+		var params = {};
+		params['project'] = XNAT.data.context.projectID;
+		params['process'] = 'true';
+		if (typeof(XNAT.app.abu.buildPath)!=='undefined' && XNAT.app.abu.buildPath != null && XNAT.app.abu.buildPath!=='') {
+			params['buildPath'] = XNAT.app.abu.buildPath;
+		}
+		if (typeof(XNAT.app.abu.configuredResource)!=='undefined' && XNAT.app.abu.configuredResource!=null) {
+			params['configuredResource'] = XNAT.app.abu.configuredResource;
+		}
+		if (typeof(this.paramData)!=='undefined' && this.paramData!=null && Object.keys(this.paramData).length>0) {
+			params['passedParameters'] = encodeURIComponent(JSON.stringify(this.paramData));
+		}
+		params['XNAT_CSRF'] = window.csrfToken;
+		if (XNAT.data.context.isSubject) {
+			params['subject'] = XNAT.data.context.subjectID;
+		}
+		if (XNAT.data.context.isExperiment || XNAT.data.context.isImageAssessor || XNAT.data.context.isImageSession || XNAT.data.context.isSubjectAssessor) {
+			params['experiment'] = XNAT.data.context.ID;
+		}
+		var eventHandler = $('#eventHandlerSelect').val();
+		if (eventHandler != undefined && eventHandler != null && eventHandler.length>0) {
+			params['eventHandler'] = eventHandler;
+		}
+		params['xsiType'] = XNAT.data.context.xsiType;
+		var queryParams = "?import-handler=" + XNAT.app.abu.importHandler + "&" + $.param(params);
+
+		var processAjax = $.ajax({
+			type : "POST",
+			url:serverRoot+"/REST/services/import" + queryParams +
+					 (($("#extractRequestBox").length>0) ? (($("#extractRequestBox").is(':checked')) ? "&extract=true" : "&extract=false") : "") +
+					 (($("#emailBox").length>0) ? (($("#emailBox").is(':checked')) ? "&sendemail=true" : "&sendemail=false") : "") +
+					 (($("#verboseBox").length>0) ? (($("#verboseBox").is(':checked')) ? "&verbose=true" : "&verbose=false") : "") +
+					 (($("#updateBox").length>0) ? (($("#updateBox").is(':checked')) ? "&update=true" : "&update=false") : "") 
+			,
+			cache: false,
+			async: true,
+			/*
+			data: JSON.stringify(this.paramData),
+			contentType: "application/json; charset=utf-8",
+			data: this.paramData,
+			contentType: "application/x-www-form-urlencoded",
+			encode: true,
+			*/
+			context: this,
+			dataType: 'text'
+		  });
+		processAjax.done( function( data, textStatus, jqXHR ) {
+			$(".abu-upload-list").css('display','none');
+			$(".abu-upload-list").html('');
+			$(".response_text").html(XNAT.app.abu.processReturnedText(data,true) + "<br><br>");
+			$(".response_text").css('display','inline');
+			abu._fileUploader.processingComplete();
+			XNAT.app.abu.buildPath='';
+			this.paramsToPass = undefined;
+			this.paramData = undefined;
+			$("#usageSelect").prop('disabled',false);
+			//if (XNAT.app.abu.usageSelect=='Upload') {
+			//	$("#resourceSelect").prop('disabled',false);
+			//} else if (XNAT.app.abu.usageSelect=='Launch') { 
+			//	$("#abu-process-button").removeClass("abu-button-disabled");
+			//}
+		});
+		processAjax.fail( function( data, textStatus, jqXHR ) {
+			$(".abu-upload-list").css('display','none');
+			$(".abu-upload-list").html('');
+			$(".response_text").html("<h3>ERROR:  Processing not successful:</h3><h3>Server Response:  " + data.statusText +  " (StatusCode=" + data.status + ")</h3>  " + data.responseText);
+			$(".response_text").css('display','inline');
+			abu._fileUploader.processingComplete();
+			XNAT.app.abu.buildPath='';
+			this.paramsToPass = undefined;
+			this.paramData = undefined;
+			//if (XNAT.app.abu.usageSelect=='Upload') {
+			//	$("#resourceSelect").prop('disabled',false);
+			//} else if (XNAT.app.abu.usageSelect=='Launch') { 
+			//	$("#abu-process-button").removeClass("abu-button-disabled");
+			//}
+		});
+		$("#abu-done-button").removeClass("abu-button-disabled");
+		setTimeout(function(){
+			if (document.getElementById("emailBox")!=null && document.getElementById("emailBox").checked) {
+				xModalMessage('Notice',"You will be sent an e-mail upon completion");
+				xModalCloseNew(XNAT.app.abu.abuConfigs.modalOpts.id);
+			}
+		},500);
+
+}
+XNAT.app.abu.processReturnedText = function(ins,returnToBr){
+	return ins.replace(/[\r\n]+/g,((returnToBr) ? "<br>" : '')).replace(/\/$/,'');
+}
+
+XNAT.app.abu.removeUploaderConfiguration=function(configEvent,scope) {
+	var isFound = false;
+	for (var i=0; i<XNAT.app.abu.uploaderConfig.length; i++) {
+		var config = XNAT.app.abu.uploaderConfig[i];
+		if (config.event == configEvent && config.eventScope == scope) {
+			isFound = true;
+			XNAT.app.abu.uploaderConfig.splice(i,1);
+			break;
+		}
+	}
+	if (isFound) {
+		XNAT.app.abu.putUploaderConfiguration(scope,false);
+	}
+}
+
+XNAT.app.abu.saveUploaderConfiguration=function(configEvent,scope) {
+	var newConfigObj = { event: configEvent, eventScope: scope };
+	newConfigObj.launchFromCacheUploads = $('#ULC_RB_launchFromCacheUploads').is(':checked');
+	newConfigObj.launchFromResourceUploads = $('#ULC_RB_launchFromResourceUploads').is(':checked');
+	newConfigObj.launchWithoutUploads = $('#ULC_RB_launchWithoutUploads').is(':checked');
+	newConfigObj.parameters = undefined;
+	$(".ULC_parametersDiv").each(function() {
+		var parameterField = $(this).find(".ULC_parametersField").val();
+		if (typeof(parameterField)!=='undefined' && parameterField != null && parameterField.replace('/ /g','').length>0) {
+			if (typeof(newConfigObj.parameters)==='undefined' || newConfigObj.parameters == null) {
+				newConfigObj.parameters = [];
+			}
+			var newParam = {};
+			newParam.name = parameterField.trim();
+			newParam.type = $(this).find(".ULC_parametersType").val();
+			newParam.required = $(this).find(".ULC_parametersRequired").is(':checked');
+			newConfigObj.parameters.push(newParam);
+		}
+	});
+	if ( ( $('#ULC_RB_launchFromResourceUploads').is(':checked') || $('#ULC_RB_launchFromCacheUploads').is(':checked') ||
+			 $('#ULC_RB_launchWithoutUploads').is(':checked')
+		 ) && !$('#ULC_contextsAllCB').is(':checked')) {
+		$(".ULC_contextsContext").each(function() {
+			var contextVal = $(this).val();
+			if (!(typeof(contextVal)==='undefined' || contextVal == null || contextVal.replace(/ /g,'').length<1)) {
+				if (typeof(newConfigObj.contexts)==='undefined' || newConfigObj.contexts == null) {
+					newConfigObj.contexts = [];
+				}
+				newConfigObj.contexts.push(contextVal.trim());
+			}
+		}); 
+	}
+	if ( $('#ULC_RB_launchFromResourceUploads').is(':checked') && !$('#ULC_resourcesAllCB').is(':checked')) {
+		var resourceOptions = $("#ULC_configuredResources option:selected");
+		if (resourceOptions.length>0) {
+			$(resourceOptions).each(function() {
+				var thisVal = $(this).val();
+				if (!(typeof(thisVal)==='undefined' || thisVal == null || thisVal.replace(/ /g,'').length<1)) {
+					if (typeof(newConfigObj.resourceConfigs)==='undefined' || newConfigObj.resourceConfigs == null) {
+						newConfigObj.resourceConfigs = [];
+					}
+					newConfigObj.resourceConfigs.push(thisVal.trim());
+				}
+			});
+		}
+	}
+	var isUpdated = false;
+	var isFound = false;
+	for (var i=0; i<XNAT.app.abu.uploaderConfig.length; i++) {
+		var config = XNAT.app.abu.uploaderConfig[i];
+		if (config.event == newConfigObj.event && config.eventScope == newConfigObj.eventScope) {
+			isFound = true;
+			if (!(JSON.stringify(XNAT.app.abu.uploaderConfig[i]) == JSON.stringify(newConfigObj))) {;
+				XNAT.app.abu.uploaderConfig[i] = newConfigObj;
+				isUpdated = true;
+			} 
+		}
+	}
+	if (!isFound) {
+		XNAT.app.abu.uploaderConfig.push(newConfigObj);
+	}
+	if (isFound && !isUpdated) { 
+		xModalMessage('Nothing done',"The configuration has not changed.  Nothing done.");
+	} else {
+		XNAT.app.abu.putUploaderConfiguration(scope,true);
+	}
+}
+
+XNAT.app.abu.putUploaderConfiguration=function(scope, notify) {
+
+	var scopeArray = [];
+	for (var i=0;i<XNAT.app.abu.uploaderConfig.length;i++) {
+		if (XNAT.app.abu.uploaderConfig[i].eventScope==scope) {
+			scopeArray.push(XNAT.app.abu.uploaderConfig[i]);
+		}
+	}
+	var uploaderConfigAjax = $.ajax({
+		type : "PUT",
+		notify : notify,
+ 		url:serverRoot+'/data' + ((scope!='site') ? '/projects/' + XNAT.data.context.project : '') +'/config/automation_uploader/configuration?inbody=true&XNAT_CSRF=' + window.csrfToken,
+		cache: false,
+		async: false,
+		data:  JSON.stringify(scopeArray),
+		contentType: "application/text; charset=utf-8"
+	 });
+	uploaderConfigAjax.done( function( data, textStatus, jqXHR ) {
+		if (notify) {
+			xModalMessage('Saved','The uploader configuration has been saved');
+		}
+	});
+	uploaderConfigAjax.fail( function( data, textStatus, error ) {
+		if (notify) {
+			xModalMessage('Error','ERROR:  Configuration was not successfully saved (' + textStatus + ')');
+		}
+	});
+}
+
+XNAT.app.abu.validateUploaderConfiguration=function() {
+	var errorList = '';
+	if ( ( $('#ULC_RB_launchFromResourceUploads').is(':checked') || $('#ULC_RB_launchFromCacheUploads').is(':checked') ||
+			 $('#ULC_RB_launchWithoutUploads').is(':checked')
+		 ) && !$('#ULC_contextsAllCB').is(':checked')) {
+		var haveContext = false;
+		var contextEles = $(".ULC_contextsContext").each(function() {
+			var contextVal = $(this).val();
+			if (!(typeof(contextVal)==='undefined' || contextVal==null || contextVal.replace(/ /g,'').length<1)) {
+				haveContext=true;
+				return false;
+			}
+		}); 
+		if (!haveContext) {
+			errorList+='<li>Usage indicates the uploader should be used with this handler, but no contexts have been specified</li>';
+		}
+	}
+	if ( $('#ULC_RB_launchFromResourceUploads').is(':checked') && !$('#ULC_resourcesAllCB').is(':checked')) {
+		if ($("#ULC_configuredResources option:selected").length<1) {
+			errorList+='<li>Usage indicates the uploader should be used with configured resources, but no configured resources have been specified</li>';
+		}
+	}
+	if (errorList.length>0) {
+		xModalMessage('Error',"<h3>ERROR:  Invalid configuration</h3>" + errorList);
+		return false;
+	}
+	return true;
+}
+
+XNAT.app.abu.configureUploaderForEventHandler=function(configEvent,scope) {
+
+	var uploaderConfig = XNAT.app.abu.uploaderConfig;
+	if (typeof(uploaderConfig) === 'undefined' || uploaderConfig == null) {
+		xModalMessage("Couldn't retrieve uploader configuration");
+		return;
+	}
+	var configObj;
+	for (var i=0;i<uploaderConfig.length;i++) {
+		var objI = uploaderConfig[i];
+		if (objI.eventScope == scope && objI.event == configEvent) {
+			// Clone the object because we may modify it (fill in context values).
+			configObj = jQuery.extend(true, {}, objI);
+		}
+	}
+	if (typeof(configObj) === 'undefined' || configObj == null) {
+		configObj = {};
+		configObj.launchFromCacheUploads = true;
+		configObj.launchFromResourceUploads = true;
+		configObj.launchWithoutUploads = true;
+		// best to leave these undefined, I think
+		//configObj.parameters = [  ];
+		//configObj.contexts = [ 'xnat:projectData','xnat:subjectAssessorData','xnat:imageAssessorData','xnat:imageSessionData','xnat:imageScanData','xnat:subjectData' ];
+		//configObj.resourceConfigs = [  ];
+	}
+	var manageModalOpts = {
+		width: 840,  
+		height: 680,  
+		id: 'xmodal-uploader-config',  
+		title: "Configure Uploader for Event Handler",
+		content: "<div id='configUploadDiv'></div>",
+		buttons: {
+			saveConfig: { 
+				label: 'Save Configuration',
+				close: false,
+				action: function( obj ){
+					if (XNAT.app.abu.validateUploaderConfiguration()) {
+						XNAT.app.abu.saveUploaderConfiguration(configEvent,scope);
+						obj.close();
+					}
+				}
+			},
+			close: { 
+				label: 'Cancel',
+				isDefault: false
+			}
+		}
+	};
+	xModalOpenNew(manageModalOpts);
+	var configHtml = '<h3>Event Handler:  ' + configEvent + '</h3>';
+	configHtml+='<p>';
+	/** NOTE:  These radio buttons were originally coded as checkboxes, assuming the same scripts might be triggered from different upload/launch contexts. **
+ 	 **        It was later decided that each event handler should only be triggered from a single upload/launch context.  The code that uses these still   **
+         **        treats them as check boxes in case we change our desired usage.                                                                              **/ 
+	configHtml+='<div style="margin-left:20px;width:100%"><p><b>Usage:</b>';
+	configHtml+='<div style="margin-left:20px;width:100%"><input type="radio" id="ULC_RB_launchFromCacheUploads" name="ULC_RB" value="launchFromCacheUploads"' +
+				 ((configObj.launchFromCacheUploads) ? ' checked' : '') + '> <b> Use for cache space uploads</b> </div>';
+	configHtml+='<div style="margin-left:20px;width:100%"><input type="radio" id="ULC_RB_launchFromResourceUploads" name="ULC_RB" value="launchFromResourceUploads"' +
+				 ((configObj.launchFromResourceUploads) ? ' checked' : '') + '> <b> Use for configured resource uploads </b> </div>';
+	configHtml+='<div style="margin-left:20px;width:100%"><input type="radio" id="ULC_RB_launchWithoutUploads" name="ULC_RB" value="launchWithoutUploads"' +
+				 ((configObj.launchWithoutUploads) ? ' checked' : '') + '> <b> Trigger without uploads </b> </div>';
+	configHtml+='</div></p>';
+	configHtml+='<p>';
+	configHtml+='<div style="margin-left:20px;width:100%"><p><b>User Supplied Parameters:</b><p><div id="ULC_parameters">';
+	for (var i=0;i<((typeof(configObj.parameters)!=='undefined' && configObj.parameters.length>0) ? configObj.parameters.length : 0);i++) {
+		var hasValue = (typeof(configObj.parameters)!=='undefined' && configObj.parameters.length>=(i+1));
+		var fieldValue = (hasValue && typeof(configObj.parameters[i].name)!==undefined) ? configObj.parameters[i].name : '';
+		var stringSelected = (hasValue && typeof(configObj.parameters[i].type)!==undefined && configObj.parameters[i].type=='String') ? 'selected' : '';
+		var integerSelected = (hasValue && typeof(configObj.parameters[i].type)!==undefined && configObj.parameters[i].type=='Integer') ? 'selected' : '';
+		var floatSelected = (hasValue && typeof(configObj.parameters[i].type)!==undefined && configObj.parameters[i].type=='Float') ? 'selected' : '';
+		var fieldRequired = (hasValue && typeof(configObj.parameters[i].required)!==undefined && configObj.parameters[i].required==false) ? '' : 'checked';
+		configHtml+='<div id="ULC_parametersDiv' + i + '" class="ULC_parametersDiv" style="margin-left:20px;margin-bottom:5px;width:100%">' +
+				 '<input type="text" size="20"  id="ULC_parametersField' + i + '"  class="ULC_parametersField" value="' + fieldValue + '">' + 
+				'  <select id="ULC_parametersType' + i  + '" class="ULC_parametersType">' + 
+				'<option value="String" ' + stringSelected + '>String</option>' + 
+				'<option value="Integer" ' + integerSelected + '>Integer</option>' + 
+				'<option value="Float" ' + floatSelected + '>Float</option>' + 
+				'</select>  ' + 
+				' <input type="checkbox" id="ULC_parametersRequired"' + i + '" ' + fieldRequired + 
+				' class="ULC_parametersRequired"> Required? <button id="ULC_parametersRemove' + i + '" class="parametersRemove">Remove</button></div>';
+	} 
+	configHtml+='</div>';
+	configHtml+='<span style="margin-left:20px"><button class="parametersAdd">Add Parameter</button></span>';
+	configHtml+='</div></p>';
+
+	var configuredResources=[];
+	var allChecked = (typeof(configObj.resourceConfigs) === 'undefined') ? 'checked' : '';
+	if (typeof(XNAT.app.abu.allResourceConfigs)!=='undefined') {
+		configuredResources=XNAT.app.abu.allResourceConfigs;
+	}
+	var selectSize = (configuredResources.length<4) ? 4 : ((configuredResources.length>10) ? 10 : configuredResources.length);
+	configHtml+='<p>';
+	configHtml+='<div style="margin-left:20px;width:100%"><p><b>Configured resources for which this handler is applicable:</b> ' + 
+				'<span style="margin-left:10px"><input type="checkbox" id="ULC_resourcesAllCB" ' + allChecked + 
+					((configObj.launchFromResourceUploads) ? '' : ' disabled="disabled"') + '>Applicable for all configured resources.</span>';
+	if (configuredResources.length<1) {
+		configHtml+='<div style="margin-left:20px;width:100%"><p><b>NONE DEFINED</b></p></div>';
+	} else  {
+		configHtml+='<div style="margin-left:20px;width:100%"><p>' + 
+		'<div style="width:100%;float:left;margin-bottom:10px;"><div style="width:auto;float:left;"> <select id="ULC_configuredResources" size=' + selectSize + ' style="max-width:500px;width:200px;" multiple ' + 
+			((allChecked || !(configObj.launchFromResourceUploads)) ? 'disabled="disabled"' : '')  + '>';
+		for (var i=0;i<configuredResources.length;i++) {
+			configHtml+='<option value="' + configuredResources[i].name + '" ' + 
+							((typeof(configObj.resourceConfigs)!=='undefined' && $.inArray(configuredResources[i].name,configObj.resourceConfigs)>=0) ? 'selected' : '') +
+				 '>' + configuredResources[i].name + '</option>';
+		} 
+		configHtml+='</select></div><span style="margin-left:10px">NOTE:  Multiple resources may be selected</span></div>';
+		configHtml+='</p></div>';
+	}
+	configHtml+='</div></p>';
+
+	configHtml+='<p>';
+	var allContextsChecked = '';
+	if (typeof(configObj.contexts)==='undefined') {
+		allContextsChecked = 'checked';
+		configObj.contexts = [ 'xnat:projectData','xnat:subjectAssessorData','xnat:imageAssessorData','xnat:imageSessionData','xnat:imageScanData','xnat:subjectData' ];
+	}
+	configHtml+='<div style="margin-left:20px;width:100%"><p><b>Context(s) for launch/upload:  <span style="margin-left:10px">' + 
+					'<input type="checkbox" id="ULC_contextsAllCB" ' + allContextsChecked + 
+					((configObj.launchFromResourceUploads || configObj.launchFromCacheUploads || configObj.launchWithoutUploads) ? '' : ' disabled="disabled"') +
+					 '>Applicable across all contexts?</span></b><p><div id="ULC_contexts">';
+	for (var i=0;i<((typeof(configObj.contexts)!=='undefined' && configObj.contexts.length>0) ? configObj.contexts.length : 0);i++) {
+		var hasValue = (typeof(configObj.contexts)!=='undefined' && configObj.contexts.length>=(i+1));
+		var contextValue = (hasValue && typeof(configObj.contexts[i])!==undefined) ? configObj.contexts[i] : '';
+		configHtml+='<div id="ULC_contextsDiv' + i + '" class="ULC_contextsDiv" style="margin-left:20px;margin-bottom:5px;width:100%">' + 
+					'<input type="text" size="30"  id="ULC_contextsContext' + i + '"  class="ULC_contextsContext" value="' + contextValue + '"> ' + 
+					' <button id="ULC_contextsRemove' + i + '" class="contextsRemove">Remove</button></div>';
+	} 
+	configHtml+='</div><span style="margin-left:20px"><button class="ULC_contextsAdd">Add Context</button></span></div></p>';
+
+	$('#configUploadDiv').html(configHtml); 
+	$(".ULC_contextsDiv :input,.ULC_contextsAdd").prop('disabled',$('#ULC_contextsAllCB').is(':checked') || !( $('#ULC_RB_launchFromResourceUploads').is(':checked') || 
+		$('#ULC_RB_launchFromCacheUploads').is(':checked') || $('#ULC_RB_launchWithoutUploads').is(':checked')) );
+
+	$('.parametersRemove').click(function(){ 
+			$("#" + this.id.replace("ULC_parametersRemove","ULC_parametersDiv")).remove(); 
+	 });
+	$('.parametersAdd').click(function(){ 
+			ULC_addParameter();
+	 });
+	$('.contextsRemove').click(function(){ 
+			$("#" + this.id.replace("ULC_contextsRemove","ULC_contextsDiv")).remove(); 
+	 });
+	$('.ULC_contextsAdd').click(function(){ 
+			ULC_addContext();
+	 });
+	$('#ULC_resourcesAllCB').change(function(){ 
+		$("#ULC_configuredResources").prop('disabled',$('#ULC_resourcesAllCB').is(':checked'));
+	 });
+	$('#ULC_contextsAllCB').change(function(){ 
+		$(".ULC_contextsDiv :input,.ULC_contextsAdd").prop('disabled',$('#ULC_contextsAllCB').is(':checked'));
+	 });
+	$('#ULC_RB_launchFromResourceUploads').change(function(){ 
+		$("#ULC_resourcesAllCB").prop('disabled',!$('#ULC_RB_launchFromResourceUploads').is(':checked'));
+		$("#ULC_configuredResources").prop('disabled',$('#ULC_resourcesAllCB').is(':checked') || !$('#ULC_RB_launchFromResourceUploads').is(':checked'));
+		$("#ULC_contextsAllCB").prop('disabled',!( $('#ULC_RB_launchFromResourceUploads').is(':checked') || $('#ULC_RB_launchFromCacheUploads').is(':checked') ||
+			 $('#ULC_RB_launchWithoutUploads').is(':checked')) );
+		$(".ULC_contextsDiv :input,.ULC_contextsAdd").prop('disabled',$('#ULC_contextsAllCB').is(':checked') || !( $('#ULC_RB_launchFromResourceUploads').is(':checked') || 
+			$('#ULC_RB_launchFromCacheUploads').is(':checked') || $('#ULC_RB_launchWithoutUploads').is(':checked')) );
+	 });
+	$('#ULC_RB_launchFromCacheUploads').change(function(){ 
+		$("#ULC_contextsAllCB").prop('disabled',!( $('#ULC_RB_launchFromResourceUploads').is(':checked') || $('#ULC_RB_launchFromCacheUploads').is(':checked') ||
+			 $('#ULC_RB_launchWithoutUploads').is(':checked')) );
+		$(".ULC_contextsDiv :input,.ULC_contextsAdd").prop('disabled',$('#ULC_contextsAllCB').is(':checked') || !( $('#ULC_RB_launchFromResourceUploads').is(':checked') || 
+			$('#ULC_RB_launchFromCacheUploads').is(':checked') || $('#ULC_RB_launchWithoutUploads').is(':checked')) );
+	 });
+	$('#ULC_RB_launchWithoutUploads').change(function(){ 
+		$("#ULC_contextsAllCB").prop('disabled',!( $('#ULC_RB_launchFromResourceUploads').is(':checked') || $('#ULC_RB_launchFromCacheUploads').is(':checked') ||
+			 $('#ULC_RB_launchWithoutUploads').is(':checked')) );
+		$(".ULC_contextsDiv :input,.ULC_contextsAdd").prop('disabled',$('#ULC_contextsAllCB').is(':checked') || !( $('#ULC_RB_launchFromResourceUploads').is(':checked') || 
+			$('#ULC_RB_launchFromCacheUploads').is(':checked') || $('#ULC_RB_launchWithoutUploads').is(':checked')) );
+	 });
+
+	var ULC_addParameter = function(){
+
+		var divs = $(".ULC_parametersDiv");
+		var fields = $(".ULC_parametersField");
+		for (var i=0;i<fields.length;i++) {
+			var val = $(fields.get(i)).val();
+			if (typeof(val)==='undefined' || val == null || val.length<1) {
+				$('#ULC_parametersField' + i).focus();
+				return;
+			}
+		}
+		var len = (divs.length>0) ? Number(($(divs).last().get(0)).id.replace("ULC_parametersDiv",""))+1 : 1;
+		$("#ULC_parameters").append(
+				'<div id="ULC_parametersDiv' + len + '" class="ULC_parametersDiv" style="margin-left:20px;margin-bottom:5px;width:100%">' +
+				'<input type="text" size="20"  id="ULC_parametersField' + i + '" class="ULC_parametersField"> ' + 
+				' <select id="ULC_parametersType' + len  + '" class="ULC_parametersType">' + 
+				'<option value="String">String</option>' + 
+				'<option value="Integer">Integer</option>' + 
+				'<option value="Float">Float</option>' + 
+				'</select>  ' + 
+				'<input type="checkbox" id="ULC_parametersRequired"' + len + '" class="ULC_parametersRequired" checked> Required? <button id="ULC_parametersRemove' + len +
+				 '" class="parametersRemove">Remove</button></div>'
+		);
+		$('#ULC_parametersRemove' + len).click(function(){ 
+			$("#ULC_parametersDiv" + len).remove(); 
+		});
+		$('#ULC_parametersField' + i).focus();
+
+	}
+
+	var ULC_addContext = function(){
+
+		var divs = $(".ULC_contextsDiv");
+		var contexts = $(".ULC_contextsContext");
+		for (var i=0;i<contexts.length;i++) {
+			var val = $(contexts.get(i)).val();
+			if (typeof(val)==='undefined' || val == null || val.length<1) {
+				$('#ULC_contextsContext' + i).focus();
+				return;
+			}
+		}
+		var len = (divs.length>0) ? Number(($(divs).last().get(0)).id.replace("ULC_contextsDiv",""))+1 : 1;
+		$("#ULC_contexts").append(
+				'<div id="ULC_contextsDiv' + len + '" class="ULC_contextsDiv" style="margin-left:20px;margin-bottom:5px;width:100%">' + 
+				'<input type="text" size="30"  id="ULC_contextsContext' + i + '" class="ULC_contextsContext">' + 
+				' <button id="ULC_contextsRemove' + len + '" class="contextsRemove">Remove</button></div>'
+		);
+		$('#ULC_contextsRemove' + len).click(function(){ 
+			$("#ULC_contextsDiv" + len).remove(); 
+		});
+		$('#ULC_contextsContext' + i).focus();
+
+	}
+}
+
+$(document).ready(XNAT.app.abu.abuConfigs.load);
+
diff --git a/src/main/webapp/scripts/uploaders/fileuploader.js b/src/main/webapp/scripts/uploaders/fileuploader.js
new file mode 100644
index 0000000000000000000000000000000000000000..e2a1868d1f01c2634081535648e6109586bc0f5e
--- /dev/null
+++ b/src/main/webapp/scripts/uploaders/fileuploader.js
@@ -0,0 +1,269 @@
+
+//
+// Helper functions
+//
+
+var abu = abu || {};
+
+abu.initializeUploader = function(initarr){
+	abu._fileUploader = new abu.FileUploader(initarr);
+};
+
+abu.FileUploader = function(o){
+
+	this._options = o;
+	// NOTE:  Multiple concurrent cache uploads works fine, but multiple concurrent uploads to a resource often causes failures and can corrupt the catalog.
+	// Leave this set to 1 if the uploader supports uploading directly to resources.
+	this.MAX_CONCURRENT_UPLOADS = 1;
+	this.ALLOW_DRAG_AND_DROP = true;
+	this.uploadsInProgress = 0;
+	this.uploadsStarted = 0;
+	$(this._options.element).html(""); 
+
+	this.buildUploaderDiv = function() {
+		$(this._options.element).append(
+			'<div class="abu-uploader">' +
+				'<div id="abu-files-processing" class="abu-files-processing">        Processing uploaded files..... </div>' +
+				'<a id="file-uploader-instructions-sel" class="abu-uploader-instructions-sel" onclick="abu._fileUploader.uploaderHelp()">?</a>' +
+				'<div class="abu-upload-drop-area" style="display: none;"><span>Drop files here to upload</span></div>' +
+				'<div class="abu-xnat-interactivity-area">' +
+				'</div>' +
+			'<div id="abu-upload-button" class="abu-upload-button" style="position: relative; overflow: hidden; direction: ltr;">' + 
+				'Upload files<input multiple="multiple" type="file" id="file-upload-input" class="abu-button-input">' + 
+			'</div>' +
+			'<div id="abu-done-button" class="abu-done-button" style="position: relative; overflow: hidden; direction: ltr;">' + 
+				'<span id="abu-done-button-text">Cancel</span><input type="image" name="done" class="abu-button-input" style="width:105px">' +
+			'</div>' +
+			'<div id="abu-process-button" class="abu-process-button " style="position: relative; overflow: hidden; direction: ltr;">' +
+				'<span id="abu-process-button-text">Process Files</span>' +
+				'<input type="image" name="process" class="abu-button-input" style="width:105px">' +
+			'</div>' +
+			'<div class="abu-options-div">' +
+			((this._options.showExtractOption) ?
+				'<div class="abu-options-cb" title = "Extract compressed files on upload (zip, tar, gz)"?>' +
+					'<input id="extractRequestBox" type="checkbox" value="1" checked="checked">' +
+					'Extract compressed files' +
+				'</div>' : 
+				'<div class="abu-extract-zip"><input id="extractRequestBox" type="hidden" value="1"/></div>'
+			) +
+			((this._options.showEmailOption) ?
+				'<div class="abu-options-cb" title = "Close window upon submit and send e-mail upon completion">' +
+					'<input id="emailBox" type="checkbox" value="1">' +
+					'Close window upon submit' +
+				'</div>' : "" 
+			) +
+			((this._options.showUpdateOption) ?
+				'<div class="abu-options-cb" title = "Update existing records?">' +
+					'<input id="updateBox" type="checkbox" value="1">' +
+					'Update existing records?' +
+				'</div>' : "" 
+			) +
+			((this._options.showVerboseOption) ?
+				'<div class="abu-options-cb" title = "Verbose status output?">' +
+					'<input id="verboseBox" type="checkbox" value="1"' +
+					'Verbose status output?' +
+				'</div>' : "" 
+			) +
+			'</div><br>' +
+			'<div class="abu-list-area"><ul class="abu-upload-list"></ul>' +
+				'<div class="response_text" style="display:none"></div>' +
+			'</div> ' 
+		); 
+		$("#abu-upload-button").mouseenter(function() { $(this).addClass("abu-upload-button-hover"); });
+		$("#abu-upload-button").mouseleave(function() { $(this).removeClass("abu-upload-button-hover"); });
+		$("#abu-done-button").click(this._options.doneFunction);
+		$("#abu-done-button").mouseenter(function() { $(this).addClass("abu-done-button-hover"); });
+		$("#abu-done-button").mouseleave(function() { $(this).removeClass("abu-done-button-hover"); });
+		$("#abu-process-button").click(this._options.processFunction);
+		$("#abu-process-button").mouseenter(function() { $(this).addClass("abu-process-button-hover"); });
+		$("#abu-process-button").mouseleave(function() { $(this).removeClass("abu-process-button-hover"); });
+		if (this.ALLOW_DRAG_AND_DROP) {
+			$(".abu-upload-drop-area").on('dragover',function(e) {
+					this.activateUploadArea(e);
+				}.bind(this)
+			);
+			$(".abu-upload-drop-area").on('dragenter',function(e) {
+					this.activateUploadArea(e);
+				}.bind(this)
+			);
+			$(".abu-upload-drop-area").on('drop',function(e) {
+					$(".abu-upload-drop-area").css('display','none');
+					$(".abu-upload-drop-area").removeClass('abu-upload-drop-area-active');
+					if(e.originalEvent.dataTransfer){
+						if(e.originalEvent.dataTransfer.files.length) {
+							e.preventDefault();
+							e.stopPropagation();
+							this.doFileUpload(e.originalEvent.dataTransfer.files);
+						}   
+					}
+				}.bind(this)
+			);
+			$(this._options.element).on('dragover',function(e) {
+					this.activateUploadArea(e);
+				}.bind(this)
+			).bind(this);
+			$(this._options.element).on('dragenter',function(e) {
+					this.activateUploadArea(e);
+				}.bind(this)
+			);
+		}	
+		$("#file-upload-input").change(function(eventData) {
+			this._options.uploadStartedFunction();
+			var fileA = eventData.target.files;
+			if (typeof eventData.target.files !== 'undefined') {
+				var fileA = eventData.target.files;
+				this.doFileUpload(fileA);
+			}
+		}.bind(this));
+	}
+
+	this.processingComplete = function() {
+		$("#abu-done-button-text").html("Done");
+		//$("#abu-process-button").css("display","None");
+		//$("#abu-upload-button").css("display","None");
+		$("#abu-process-button").addClass("abu-button-disabled");
+		$("#abu-upload-button").addClass("abu-button-disabled");
+		$("#abu-files-processing").css("display","None");
+	}
+
+	this.doFileUpload = function(fileA) {
+		var start_i = $('form[id^=file-upload-form-]').length;				
+		for (var i=0; i<fileA.length; i++) {
+			var cFile = fileA[i];
+			var adj_i = i + start_i;
+			$(".abu-upload-list").append(
+				'<form id="file-upload-form-' + adj_i + '" action="' + this._currentAction +
+					 (($("#extractRequestBox").length>0) ? (($("#extractRequestBox").is(':checked')) ? "&extract=true" : "&extract=false") : "") +
+					 (($("#emailBox").length>0) ? (($("#emailBox").is(':checked')) ? "&sendemail=true" : "&sendemail=false") : "") +
+					 (($("#verboseBox").length>0) ? (($("#verboseBox").is(':checked')) ? "&verbose=true" : "&verbose=false") : "") +
+					 (($("#updateBox").length>0) ? (($("#updateBox").is(':checked')) ? "&update=true" : "&update=false") : "") +
+					 '" method="POST" enctype="multipart/form-data">' + 
+				'</form>' + 
+				'<div id="file-info-div-' + adj_i + '"><span class="abu-upload-file">' + cFile.name + '</span><span class="abu-upload-file">' +  
+					" (" + ((typeof cFile.type !== 'undefined' && cFile.type !== '') ? cFile.type + ", " : '') +
+					 this.bytesToSize(cFile.size) + ") </span>" +  
+					'<div class="abu-progress">' +
+						'<div class="abu-bar"></div >' +
+						'<div class="abu-percent">0%</div >' +
+					'</div>' +
+					'<div id="upload-status-div-' + adj_i + '" class="abu-status"></div>' +
+				'</div>');
+			var formData = new FormData();
+			formData.append("file" + adj_i,cFile,cFile.name);
+			this.uploadFile("#file-upload-form-" + adj_i,formData);
+			this.manageUploads();
+		}
+	}.bind(this)
+
+	this.bytesToSize = function(bytes) {
+	   var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
+	   if (bytes == 0) return '0 Byte';
+	   var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
+	   return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i];
+	}
+
+	this.activateUploadArea = function(e) {
+				$(".abu-upload-drop-area").css('display','inline-block');
+				$(".abu-upload-drop-area").addClass('abu-upload-drop-area-active');
+				try { 
+					e.preventDefault();
+					e.stopPropogation();
+				} catch(e) { /* Do nothing */ }
+	}
+
+	this.uploadFile = function(formSelector,formData) {
+		var infoSelector = formSelector.replace("-upload-form-","-info-div-");
+		var bar = $(infoSelector).find(".abu-bar");
+		var percent = $(infoSelector).find(".abu-percent");
+		var status = $(infoSelector).find(".abu-status");
+		$(formSelector).on("submit",function(e, uploader) {
+			 $(this).ajaxSubmit({
+				beforeSend: function(arr, $form, options) {
+					$form.data = formData;
+					$form.processData=false;
+					$form.contentType=false;
+					status.empty();
+					var percentVal = '0%';
+					bar.width(percentVal)
+					percent.html(percentVal);
+					uploader.uploadsInProgress++;
+					uploader.uploadsStarted++;
+				},
+				uploadProgress: function(event, position, total, percentComplete) {
+					var percentVal = percentComplete + '%';
+					bar.width(percentVal)
+					percent.html(percentVal);
+				},
+				error: function(result) {
+					$(status).data("rtn",result);
+			 		status.html('<a href="javascript:abu._fileUploader.showReturnedText(\'' + $(status).attr('id') + '\')" class="underline abu-upload-fail">Failed</a>');
+			 		status.css("display","inline-block");
+			 		$(infoSelector).find(".abu-progress").css("display","none");
+					$("#abu-done-button").removeClass("abu-done-button-disabled");
+				},
+				success: function(result) {
+					$(status).data("rtn",result);
+					var percentVal = '100%';
+					bar.width(percentVal)
+					percent.html(percentVal);
+					// Don't create results link if we're just returning the build path
+					if (typeof result.status !== 'undefined' || result.length > 150) {
+			 			status.html('<a href="javascript:abu._fileUploader.showReturnedText(\'' + $(status).attr('id') + '\')" class="underline abu-upload-complete abu-upload-complete-text">Upload complete</a>');
+					} else {
+			 			status.html('<span class="abu-upload-complete abu-upload-complete-text">Upload complete</span>');
+					}
+			 		status.css("display","inline-block");
+			 		$(infoSelector).find(".abu-progress").css("display","none");
+				},
+				complete: function(xhr) {
+					uploader.uploadsInProgress--;
+					if (uploader.uploadsInProgress==0) {
+						uploader._options.uploadCompletedFunction();
+					}
+					uploader.manageUploads();
+				}
+			}); 
+			return false;
+		}); 
+	}
+
+	this.showReturnedText = function(ele) {
+		var eleData = $('#' + ele).data('rtn');
+		xModalMessage("Server Response",((typeof eleData.status !=='undefined') ? "<h3>RETURN CODE: " + eleData.status + " (" + eleData.statusText + ")</h3><br>" +
+				 eleData.responseText : eleData), undefined, {height:"400px",width:"800px"});
+	}
+
+	this.manageUploads = function() {
+		var MAX_CONCURRENT_UPLOADS = this.MAX_CONCURRENT_UPLOADS;
+		for (var i=1;i<=this.MAX_CONCURRENT_UPLOADS;i++) {
+			var uploadsRequested = $('form[id^=file-upload-form-]').length;				
+			var uploadsInProgress = this.uploadsInProgress;
+			var uploadsStarted = this.uploadsStarted;
+			if (uploadsInProgress < MAX_CONCURRENT_UPLOADS && uploadsStarted<uploadsRequested) {
+				$("#file-upload-form-" + uploadsStarted).trigger("submit",this);
+			} else if (uploadsStarted>=uploadsRequested) {
+				break;
+			} 
+		}
+	}.bind(this)
+
+	this.uploaderHelp=function() {
+		var templateV=
+			'<div id="file-uploader-instructions" class="abu-uploader-instructions">' + 
+			'<h3>Instructions</h3>' + 
+			'<ul>' +  
+			'<li>To upload, click the <b>Upload Files</b> button or drag files into the space below the buttons. (Drag-and-drop is supported in FF, Chrome.)</li>' +
+			((this._options.maxFiles == 1) ?
+				'<li>This uploader supports only a single file upload</li>' :
+				'<li>Multiple files may be selected</li>'
+			) +
+			'<li>Uploads will begin automatically</li>' + 
+			'<li>Upload of directories is not supported</li>' + 
+			'<li>When finished uploading, press <b>Process Files</b> to process the uploaded files</li>' + 
+			'</ul>' + 
+			'</div>';
+		xModalMessage("Uploader Instructions",templateV, undefined, {height:"400px",width:"800px"});
+	}.bind(this)
+
+}
+
diff --git a/src/main/webapp/scripts/xnat/app/automation.js b/src/main/webapp/scripts/xnat/app/automation.js
index d7fea4172765a20617b3f67a5026b0b34e50fd8f..fef6c53599b8de1df3178ff86af470f59647b619 100644
--- a/src/main/webapp/scripts/xnat/app/automation.js
+++ b/src/main/webapp/scripts/xnat/app/automation.js
@@ -219,7 +219,7 @@ XNAT.app.automation = {};
             data.event_label = data.event_id;
         }
         xhr.put({
-            url: eventsURL('', '', false),
+            url: eventsURL('', '?XNAT_CSRF=' + window.csrfToken, false),
             //url: eventsURL(),
             data: data,
             success: function(){
@@ -343,7 +343,7 @@ XNAT.app.automation = {};
     }
 
     automation.deleteEvent = function(id, label){
-        var _url =  eventsURL('', '?cascade=true', false);
+        var _url =  eventsURL('', '?XNAT_CSRF=' + window.csrfToken + '&cascade=true', false);
         xmodal.confirm({
             title: 'Delete?',
             content: 'Are you sure you want to delete the Event' +
diff --git a/src/main/webapp/scripts/xnat/app/eventsManager.js b/src/main/webapp/scripts/xnat/app/eventsManager.js
index bddc4b324d743308f11551905bb8d98b0cca8e04..6bb1803c819331f62565cef3000e88125c0f137e 100644
--- a/src/main/webapp/scripts/xnat/app/eventsManager.js
+++ b/src/main/webapp/scripts/xnat/app/eventsManager.js
@@ -20,13 +20,18 @@ $(function(){
         $no_events_defined = $('#no_events_defined'),
         $no_event_handlers = $('#no_event_handlers'),
         $add_event_handler = $('#add_event_handler'),
+        $manage_event_handlers = $('#manage_event_handlers'),
         handlersRendered = false,
         hasEvents = false;
 
-    function initEventsTable(){
+    function initEventsTable(doEdit){
+
+        if (doEdit) {
+            var events_manage_table = $('#events_manage_table');
+        }
 
         // hide stuff
-        $events_table.hide();
+        $((doEdit) ? events_manage_table : $events_table).hide();
         $no_event_handlers.hide();
 
         // Now get all the data and stick it in the table.
@@ -49,14 +54,20 @@ $(function(){
                             '<td class="event-id">' + _event_id + '</td>' +
                             '<td class="script-id">' + eventHandler.scriptId + '</td>' +
                             '<td class="description">' + eventHandler.description + '</td>' +
-                            '<td style="text-align: center;">' +
-                            '<a href="javascript:" class="delete-handler" ' +
-                            'data-handler="' + eventHandler.triggerId + '" title="Delete handler for event ' + _event_id + '">delete</a>' +
-                            '</td>' +
+                            ((doEdit) ?
+                                '<td style="text-align: center;">' +
+                                '<a href="javascript:" class="delete-handler" ' +
+                                'data-handler="' + eventHandler.triggerId + '" title="Delete handler for event ' + _event_id + '">delete</a>' +
+                                '</td>' + 
+                                '<td style="text-align: center;">' +
+                                '<a href="javascript:" class="configure-uploader-handler" ' +
+                                'data-handler="' + eventHandler.event + '" title="Delete handler for event ' + _event_id + '">configure uploader</a>' +
+                                '</td>' 
+                            : '') +
                             '</tr>';
                     });
-                    $events_table.find('tbody').html(eventRows);
-                    $events_table.show();
+                    $((doEdit) ? events_manage_table : $events_table).find('tbody').html(eventRows);
+                    $((doEdit) ? events_manage_table : $events_table).show();
                 }
                 else {
                     if (!hasEvents) {
@@ -125,7 +136,7 @@ $(function(){
                 // render the events table stuff after the
                 // request to get the events
                 if (!handlersRendered){
-                    initEventsTable();
+                    initEventsTable(false);
                 }
             }
         });
@@ -179,14 +190,19 @@ $(function(){
         }
 
         xhr.put({
-            url: XNAT.url.restUrl('/data/projects/' + window.projectScope + '/automation/handlers', null, false),
+            url: XNAT.url.restUrl('/data/projects/' + window.projectScope + '/automation/handlers?XNAT_CSRF=' + window.csrfToken, null, false),
             data: data,
             dataType: "json",
             success: function(){
                 xmodal.message('Success', 'Your event handler was successfully added.', 'OK', { 
                         action: function(){
-                            initEventsTable();
-                            xmodal.closeAll();
+                            initEventsTable(false);
+                            if ($("#events_manage_table").length>0) {
+                                initEventsTable(true);
+                            }
+                            xmodal.closeAll($(xmodal.dialog.open),$('#xmodal-manage-events'));
+                            // Trigger automation uploader to reload handlers
+                            XNAT.app.abu.getAutomationHandlers();
                         }  
                     }
                 );
@@ -194,13 +210,62 @@ $(function(){
             error: function( request, status, error ){
                 xmodal.message('Error', 'An error occurred: [' + status + '] ' + error, 'Close', {
                     action: function(){
-                        xmodal.closeAll()
+                        xmodal.closeAll($(xmodal.dialog.open),$('#xmodal-manage-events'));
                     }
                 });
             }
         });
     }
 
+ 
+    function manageEventHandlers(){
+
+			var manageModalOpts = {
+				width: 840,  
+				height: 480,  
+				id: 'xmodal-manage-events',  
+				title: "Manage Event Handlers",
+				content: "<div id='manageModalDiv'></div>",
+                buttons: {
+                    close: { 
+                        label: 'Done',
+                        isDefault: true
+                    },
+                    addEvents: { 
+                        label: 'Add Event Handler',
+                        action: function( obj ){
+                            addEventHandler();
+                        }
+                    }
+                }
+			};
+			xModalOpenNew(manageModalOpts);
+			$('#manageModalDiv').html(
+               '<p id="no_events_defined" style="display:none;padding:20px;">There are no events currently defined for this site.</p>' +
+                '<p id="no_event_handlers" style="display:none;padding:20px;">There are no event handlers currently configured for this project.</p>' +
+                '<table id="events_manage_table" class="xnat-table" style="display:table;width:100%">' +
+                    '<thead>' +
+                    '<th>Event</th>' +
+                    '<th>Script ID</th>' +
+                    '<th>Description</th>' +
+                    '<th></th>' +
+                    '<th></th>' +
+                    '</thead>' +
+                    '<tbody>' +
+                    '</tbody>' +
+                '</table>' 
+           ); 
+           initEventsTable(true);
+           $("#events_manage_table").on('click', 'a.delete-handler', function(){
+               deleteEventHandler($(this).data('handler'))
+           });
+           $("#events_manage_table").on('click', 'a.configure-uploader-handler', function(){
+               XNAT.app.abu.configureUploaderForEventHandler($(this).data('handler'),'prj')
+           });
+
+    }
+
+
     function addEventHandler(){
 
         var getEvents = initEventsMenu();
@@ -235,7 +300,7 @@ $(function(){
     }
 
     function doDeleteTrigger( triggerId ){
-        var url = XNAT.url.restUrl('/data/automation/triggers/' + triggerId);
+        var url = XNAT.url.restUrl('/data/automation/triggers/' + triggerId + "?XNAT_CSRF=" + window.csrfToken);
         if (window.jsdebug) console.log(url);
         jQuery.ajax({
             type: 'DELETE',
@@ -283,6 +348,6 @@ $(function(){
     });
 
     // *javascript* event handler for adding an XNAT event handler (got it?)
-    $add_event_handler.on('click', addEventHandler);
+    $manage_event_handlers.on('click', manageEventHandlers);
 
 });
diff --git a/src/main/webapp/scripts/xnat/app/siteEventsManager.js b/src/main/webapp/scripts/xnat/app/siteEventsManager.js
index dff1aa331b7c6fd4fcd9bc8bf660715c5e885cb2..45ef98e089379a6b0ec97ca56b130889c3bd4b20 100644
--- a/src/main/webapp/scripts/xnat/app/siteEventsManager.js
+++ b/src/main/webapp/scripts/xnat/app/siteEventsManager.js
@@ -19,14 +19,19 @@ $(function(){
         $no_events_defined = $('#no_events_defined'),
         $no_event_handlers = $('#no_event_handlers'),
         $add_event_handler = $('#add_event_handler'),
+        $manage_event_handlers = $('#manage_event_handlers'),
         //handlersRendered = false,
         hasEvents = false;
     
 
-    function initHandlersTable(){
+    function initHandlersTable(doEdit){
+
+        if (doEdit) {
+            var events_manage_table = $('#events_manage_table');
+        }
 
         // hide stuff
-        $events_table.hide();
+        $((doEdit) ? events_manage_table : $events_table).hide();
         $no_event_handlers.hide();
 
         // Now get all the data and stick it in the table.
@@ -49,15 +54,22 @@ $(function(){
                             '<td class="event-id">' + _event_id + '</td>' +
                             '<td class="script-id">' + eventHandler.scriptId + '</td>' +
                             '<td class="description">' + eventHandler.description + '</td>' +
+                            ((doEdit) ?
                             '<td style="text-align: center;">' +
                             '<a href="javascript:" class="delete-handler" ' +
                             'data-event="' + _event_id + '" ' +
                             'data-handler="' + eventHandler.triggerId + '" title="Delete handler for event ' + _event_id + '">delete</a>' +
-                            '</td>' +
+                            '</td>' + 
+                            '<td style="text-align: center;">' +
+                            '<a href="javascript:" class="configure-uploader-handler" ' +
+                            'data-event="' + _event_id + '" ' +
+                            'data-handler="' + eventHandler.event + '" title="Configure uploader for event handler ' + _event_id + '">configure uploader</a>' +
+                            '</td>' 
+                             : '' ) +
                             '</tr>';
                     });
-                    $events_table.find('tbody').html(eventRows);
-                    $events_table.show();
+                    $((doEdit) ? events_manage_table : $events_table).find('tbody').html(eventRows);
+                    $((doEdit) ? events_manage_table : $events_table).show();
                 }
                 else {
                     $no_event_handlers.show();
@@ -71,9 +83,56 @@ $(function(){
             }
         });
     }
-    initHandlersTable();
+    initHandlersTable(false);
     siteEventsManager.initHandlersTable = initHandlersTable;
 
+    function manageEventHandlers(){
+
+			var manageModalOpts = {
+				width: 840,  
+				height: 480,  
+				id: 'xmodal-manage-events',  
+				title: "Manage Event Handlers",
+				content: "<div id='manageModalDiv'></div>",
+                buttons: {
+                    close: { 
+                        label: 'Done',
+                        isDefault: true
+                    },
+                    addEvents: { 
+                        label: 'Add Event Handler',
+                        action: function( obj ){
+                            addEventHandler();
+                        }
+                    }
+                }
+			};
+			xModalOpenNew(manageModalOpts);
+			$('#manageModalDiv').html(
+               '<p id="no_events_defined" style="display:none;padding:20px;">There are no events currently defined for this site.</p>' +
+                '<p id="no_event_handlers" style="display:none;padding:20px;">There are no event handlers currently configured for this project.</p>' +
+                '<table id="events_manage_table" class="xnat-table" style="display:table;width:100%">' +
+                    '<thead>' +
+                    '<th>Event</th>' +
+                    '<th>Script ID</th>' +
+                    '<th>Description</th>' +
+                    '<th></th>' +
+                    '<th></th>' +
+                    '</thead>' +
+                    '<tbody>' +
+                    '</tbody>' +
+                '</table>' 
+           ); 
+           initHandlersTable(true);
+           $("#events_manage_table").on('click', 'a.delete-handler', function(){
+               deleteEventHandler($(this).data('handler'), $(this).data('event'))
+           });
+           $("#events_manage_table").on('click', 'a.configure-uploader-handler', function(){
+               XNAT.app.abu.configureUploaderForEventHandler($(this).data('handler'),'site')
+           });
+
+    }
+
     function initEventsMenu(){
         siteEventsManager.events = []; // reset array
         return xhr.getJSON({
@@ -180,22 +239,30 @@ $(function(){
         }
 
         xhr.put({
-            url: XNAT.url.restUrl('/data/automation/handlers', null, false),
+            url: XNAT.url.restUrl('/data/automation/handlers?XNAT_CSRF=' + window.csrfToken, null, false),
             data: data,
             dataType: "json",
-            success: function(){
-                xmodal.message('Success', 'Your event handler was successfully added.', 'OK', {
-                        action: function(){
-                            initHandlersTable();
-                            xmodal.closeAll(); // close 'parent' dialog
-                        }
+            success: function(e){
+                initHandlersTable(false);
+                if ($("#events_manage_table").length>0) {
+                    initHandlersTable(true);
+                }
+                xmodal.message('Success', 'The event handler was successfully added.', 'OK', {
+                    action: function(){
+                        xmodal.closeAll($(xmodal.dialog.open),$('#xmodal-manage-events'));
                     }
-                );
+                });
+                // Trigger automation uploader to reload handlers
+                if (typeof(XNAT.app.abu.uploaderConfig)==='undefined') {
+			XNAT.app.abu.initUploaderConfig();
+		}
+		XNAT.app.abu.removeUploaderConfiguration(this.event,'site');
+		XNAT.app.abu.getAutomationHandlers();
             },
             error: function( request, status, error ){
                 xmodal.message('Error', 'An error occurred: [' + status + '] ' + error, 'Close', {
                     action: function(){
-                        xmodal.closeAll()
+                        xmodal.closeAll($(xmodal.dialog.open),$('#xmodal-manage-events'));
                     }
                 });
             }
@@ -243,7 +310,7 @@ $(function(){
     }
 
     function doDeleteHandler( handlerId ){
-        var url = XNAT.url.restUrl('/data/automation/triggers/' + handlerId, null, false);
+        var url = XNAT.url.restUrl('/data/automation/triggers/' + handlerId + "?XNAT_CSRF=" + window.csrfToken, null, false);
         if (window.jsdebug) console.log(url);
         xhr.delete({
             //type: 'DELETE',
@@ -293,5 +360,6 @@ $(function(){
 
     // *javascript* event handler for adding an XNAT event handler (got it?)
     $add_event_handler.on('click', addEventHandler);
+    $manage_event_handlers.on('click', manageEventHandlers);
 
 });
diff --git a/src/main/webapp/style/uploaders/fileuploader.css b/src/main/webapp/style/uploaders/fileuploader.css
new file mode 100644
index 0000000000000000000000000000000000000000..c33d6833aed5b82c9314c59d0b83778f54438466
--- /dev/null
+++ b/src/main/webapp/style/uploaders/fileuploader.css
@@ -0,0 +1,127 @@
+.abu-uploader { position:relative; width: 100%; height:435px;}
+
+div.abu-button-disabled {
+    background:#CCCCCC; pointer-events:none 
+}
+
+div.abu-xnat-interactivity-area {
+    display:block; /* or inline-block */
+    width: 100%; padding: 7px 0; text-align:center; margin-left:10px; float:left;    
+    background:#fff; border-bottom:1px solid #fff;color:#000;
+}
+div.abu-xnat-interactivity-area-contents {
+    display:inline; padding: 7px 0; text-align:right; margin-left:10px; margin-right:10px; float:right; width: 100%;    
+}
+div.abu-xnat-interactivity-area-sub {
+    display:inline; text-align:right; margin-left:10px; margin-right:10px; margin-top:0px; margin-bottom:5px; float:right; width: 100%;    
+}
+
+.abu-done-button {
+    display:block; /* or inline-block */
+    width: 105px; padding: 7px 0; text-align:center; margin-left:10px; float:left;    
+    background:#084FAB; border-bottom:1px solid #ddd;color:#fff; 
+}
+.abu-done-button-hover {background:#0B6BE8;}
+.abu-done-button-focus {outline:1px dotted black;}
+
+.abu-process-button {
+    display:block; /* or inline-block */
+    width: 105px; padding: 7px 0; text-align:center; margin-left:10px; float:left;    
+    background:#084FAB; border-bottom:1px solid #ddd;color:#fff; 
+}
+.abu-process-button-hover {background:#0B6BE8;}
+.abu-process-button-focus {outline:1px dotted black;}
+
+.abu-upload-button {
+    display:block; /* or inline-block */
+    width: 105px; padding: 7px 0; text-align:center; margin-left:10px; float:left;    
+    background:#084FAB; border-bottom:1px solid #ddd;color:#fff;
+}
+.abu-upload-button-hover {background:#0B6BE8;}
+.abu-upload-button-focus {outline:1px dotted black;}
+
+.abu-upload-drop-area {
+    position:absolute; top:0; left:0; width:100%; height:100%; min-height: 70px; z-index:2;
+    background:/*#0C77FF*/#0B6BE8; color:#FFFFFF; text-align:center; display:none; 
+}
+.abu-upload-drop-area span {
+    display:block; position:absolute; top: 50%; width:100%; margin-top:-8px; font-size:16px;
+}
+.abu-upload-drop-area-active {background:#084FAB; color#FFFFFF; display:inline-block; }
+
+.abu-files-processing {
+	background:#FFF url('../../images/loading.gif') 50% center no-repeat; opacity: 0.8; 
+	color:#339933; width:100%; height:100%; text-align:center; position:absolute;font-weight:bold; font-size:16px; z-index:999; display:none; float:left;
+}
+
+.abu-button-input {
+	position: absolute; right: 0px; top: 0px; font-family: Arial; font-size: 118px; margin: 0px; padding: 0px; cursor: pointer; opacity: 0;
+}
+
+.abu-list-area {position:relative;float:left;width:auto;overflow:auto;height:auto;width:100%}
+ad
+
+.abu-upload-list {margin:15px 35px; padding:0; list-style:disc;}
+.abu-upload-list li { margin:0; padding:0; line-height:15px; font-size:12px; }
+.abu-upload-file, .abu-upload-spinner, .abu-upload-size, .abu-upload-uploading-text, .abu-upload-cancel, .abu-upload-failed-text, .abu-indeterminate-text, .abu-upload-complete-text, .abu-upload-duplicate-text {
+    margin-right: 7px;
+}
+
+.abu-upload-file {}
+.abu-upload-spinner {display:inline-block; background: url("../../images/loading.gif"); width:15px; height:15px; vertical-align:text-bottom;}
+.abu-upload-size,.abu-upload-cancel {font-size:11px;}
+
+.abu-upload-failed-text {display:none;}
+.abu-upload-uploading-text {display:none;}
+.abu-upload-indeterminate-text {display:none;}
+.abu-upload-complete-text {display:none;}
+.abu-upload-duplicate-text {display:none;}
+.abu-upload-fail  {display:inline;color:#880000}
+.abu-upload-fail.abu-upload-failed-text {display:inline;color:#880000}
+.abu-upload-indeterminate .abu-upload-indeterminate-text {display:inline;}
+.abu-upload-uploading {display:inline;}
+.abu-upload-uploading.abu-upload-uploading-text {display:inline;}
+.abu-upload-complete {display:inline;color:#008800}
+.abu-upload-complete.abu-upload-complete-text {display:inline;color:#008800}
+.abu-upload-duplicate {display:inline;color:#008800}
+.abu-upload-duplicate.abu-upload-duplicate-text {display:inline;color:#008800}
+
+.abu-uploader-instructions-sel {margin-bottom:0px; margin-right:10px; display:inline: width:100%; float:right; background-color:#CCCCCC; border-color:#084FAB; color:#084FAB; border-style:solid; border-width:1px; border-color:#084FAB; font-size:14px; font-weight: bold; padding:2px; }
+.abu-uploader-instructions {margin-bottom:10px; display:inline}
+
+.abu-return-floatmessage {position:absolute;z-index:10001;width:auto;cursor:pointer;background-color:#FFFFCC; }
+.abu-return-message {width:auto;cursor:pointer;background-color:#FFFFCC; }
+
+.abu-options-div { position: relative; float: left; }
+.abu-options-cb {
+    display:block; /* or inline-block */
+    width: auto; padding: 7px 0; text-align:center; margin-left:10px; float:left;    
+}
+
+.abu-progress { position:relative; display:inline-block; width:250px; height:15px; border: 1px solid #ddd; padding: 0px; border-radius: 0px; margin-left: 10px; }
+.abu-bar { background-color: #0854AB; opacity:0.40; width:0%; height:15px; border-radius: 0px; display: inline-block }
+.abu-percent { position:absolute; display:inline-block; top:0px; left:48%; line-height: 15px; }
+.abu-status { position:relative; display:none; }
+
+.opaque {
+	opacity:0.5;
+        -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";
+        filter: alpha(opacity=50);
+   }
+.modal {
+        background-color: white;
+        height: 100%;
+        min-height: 100%;
+        margin-left: -50%; 
+        padding: 20px;
+        position: fixed; top: 60px; left: 50%;
+        width: 100%;
+        z-index: 1001;
+}
+.hidden { display: none !important; }
+
+.underline {text-decoration: underline; }
+
+.passParamText { margin-left: 30px; margin-right: 10px; }
+
+.interactivityAreaSpan { margin-left: 15px; margin-right: 10px; }
diff --git a/src/main/webapp/xnat-templates/screens/Scripts.vm b/src/main/webapp/xnat-templates/screens/Scripts.vm
index 8a6776ca71745e32e8a9826a192b013063cea2fd..b6111558fe89fd53105b46470ee5a55ec8ff1ce9 100644
--- a/src/main/webapp/xnat-templates/screens/Scripts.vm
+++ b/src/main/webapp/xnat-templates/screens/Scripts.vm
@@ -27,7 +27,7 @@
 
 <div class="yui-skin-sam">
 
-##    <div id="tp_fm" style="display:none"></div>
+    <div id="tp_fm" style="display:none"></div>
 
     #if($data.getSession().getAttribute("user").checkRole("Administrator"))
 
@@ -43,6 +43,7 @@
         </style>
 
         <script type="text/javascript" src="$content.getURI("scripts/xnat/app/siteEventsManager.js")"></script>
+        <script type="text/javascript" src="$content.getURI("scripts/uploaders/AutomationBasedUploader.js")"></script>
 
         <div id="addEventHandler" class="html-template">
             <table>
@@ -125,7 +126,6 @@
                                 <th>Event</th>
                                 <th>Script ID</th>
                                 <th>Description</th>
-                                <th></th>
                                 </thead>
                                 <tbody>
                                 ## content populated with XNAT.app.eventsManager.initEventsTable()
@@ -133,7 +133,7 @@
                             </table>
                             <br>
                             <b style="padding:0 8px;">Create a site-wide Event Handler: </b>
-                            <button type="button" id="add_event_handler" class="btn1" style="font-size:12px;" title="add an event handler">Add Event Handler</button>
+                            <button type="button" id="manage_event_handlers" class="btn1" style="font-size:12px;" title="manage event handlers">Manage Event Handlers</button>
                         </div>
 
 
diff --git a/src/main/webapp/xnat-templates/screens/xnat_imageAssessorData/actionsBox/CustomUploader.vm b/src/main/webapp/xnat-templates/screens/xnat_imageAssessorData/actionsBox/CustomUploader.vm
deleted file mode 100644
index 673b85890bc9cb706cd95dfae95d7f10e72277f1..0000000000000000000000000000000000000000
--- a/src/main/webapp/xnat-templates/screens/xnat_imageAssessorData/actionsBox/CustomUploader.vm
+++ /dev/null
@@ -1,9 +0,0 @@
-#if($om.canEdit($user))
- <li id="cru_adl_files_li" class="yuimenuitem">
-    <a id="cru_adl_files_a" class="yuimenuitemlabel uploadLink" style="display:none" href="" onclick="return false;" data-type="$om.getXSIType()" data-uri="$content.getURI("/data/experiments/$om.getImagesessionId()/assessors/$om.getId()")">Upload Additional Files</A>
-   </li>
-    <script type="text/javascript" src="$content.getURI("scripts/uploaders/ConfiguredResourceUploader.js")"></script>
-    <script>
-		XNAT.app.crConfigs.project='$!om.getProject()';
-    </script>
-#end
\ No newline at end of file
diff --git a/src/main/webapp/xnat-templates/screens/xnat_imageAssessorData/actionsBox/RunScript.vm b/src/main/webapp/xnat-templates/screens/xnat_imageAssessorData/actionsBox/RunScript.vm
new file mode 100644
index 0000000000000000000000000000000000000000..a6a15adde2cc1a6af49d6edd0b7819aad63694d5
--- /dev/null
+++ b/src/main/webapp/xnat-templates/screens/xnat_imageAssessorData/actionsBox/RunScript.vm
@@ -0,0 +1,5 @@
+#if($om.canEdit($user))
+ <li id="abu_adl_runscript_li" class="yuimenuitem">
+    <a id="abu_adl_runscript_a" class="ablLink uploadLink" style="display:block" href="" onclick="return false;" data-type="$om.getXSIType()" data-uri="$content.getURI("/data/experiments/$om.getImagesessionId()/assessors/$om.getId()")">Run Automation Script</a>
+ </li>
+#end
diff --git a/src/main/webapp/xnat-templates/screens/xnat_imageAssessorData/actionsBox/UploadFiles.vm b/src/main/webapp/xnat-templates/screens/xnat_imageAssessorData/actionsBox/UploadFiles.vm
new file mode 100644
index 0000000000000000000000000000000000000000..4042bbe6a3f7775a6aee34f75c790bcf8de29498
--- /dev/null
+++ b/src/main/webapp/xnat-templates/screens/xnat_imageAssessorData/actionsBox/UploadFiles.vm
@@ -0,0 +1,17 @@
+#if($om.canEdit($user))
+ <li id="abu_adl_files_li" class="yuimenuitem">
+    <a id="abu_adl_files_a" class="abuLink uploadLink" style="display:block" href="" onclick="return false;" data-type="$om.getXSIType()" data-uri="$content.getURI("/data/experiments/$om.getImagesessionId()/assessors/$om.getId()")">Upload Additional Files</a>
+ </li>
+ <script type="text/javascript" src="$content.getURI("scripts/uploaders/AutomationBasedUploader.js")"></script>
+ <script type="text/javascript" src="$content.getURI("scripts/uploaders/fileuploader.js")"></script>
+ <script type="text/javascript" src="$content.getURI("scripts/lib/jquery-plugins/jquery.form.js")"></script>
+ <link type="text/css" rel="stylesheet" href="$content.getURI("style/uploaders/fileuploader.css")">
+#### Link to the former custom uploader
+## <li id="cru_adl_files_li" class="yuimenuitem">
+##    <a id="cru_adl_files_a" class="yuimenuitemlabel uploadLink" style="display:none" href="" onclick="return false;" data-type="$om.getXSIType()" data-uri="$content.getURI("/data/experiments/$om.getImagesessionId()/assessors/$om.getId()")">Upload Files (Custom)</A>
+##   </li>
+##    <script type="text/javascript" src="$content.getURI("scripts/uploaders/ConfiguredResourceUploader.js")"></script>
+##    <script>
+##		XNAT.app.crConfigs.project='$!om.getProject()';
+##    </script>
+#end
diff --git a/src/main/webapp/xnat-templates/screens/xnat_projectData/actionsBox/CustomUploader.vm b/src/main/webapp/xnat-templates/screens/xnat_projectData/actionsBox/CustomUploader.vm
deleted file mode 100644
index 8346f71f105d80b1d7f68f4a5a643a5102c04cef..0000000000000000000000000000000000000000
--- a/src/main/webapp/xnat-templates/screens/xnat_projectData/actionsBox/CustomUploader.vm
+++ /dev/null
@@ -1,9 +0,0 @@
-#if($om.canEdit($user))
- <li id="cru_adl_files_li" class="yuimenuitem">
-    <a id="cru_adl_files_a" class="yuimenuitemlabel uploadLink" style="display:none" href="" onclick="return false;" data-type="xnat:projectData" data-uri="$content.getURI("/data/projects/$om.getId()")">Upload Additional Files</A>
-   </li>
-    <script type="text/javascript" src="$content.getURI("scripts/uploaders/ConfiguredResourceUploader.js")"></script>
-    <script>
-		XNAT.app.crConfigs.project='$!om.getId()';
-    </script>
-	#end
\ No newline at end of file
diff --git a/src/main/webapp/xnat-templates/screens/xnat_projectData/actionsBox/RunScript.vm b/src/main/webapp/xnat-templates/screens/xnat_projectData/actionsBox/RunScript.vm
new file mode 100644
index 0000000000000000000000000000000000000000..3848abaff7f102b5b181ec7271981adff86379bc
--- /dev/null
+++ b/src/main/webapp/xnat-templates/screens/xnat_projectData/actionsBox/RunScript.vm
@@ -0,0 +1,5 @@
+#if($om.canEdit($user))
+ <li id="abu_adl_runscript_li" class="yuimenuitem">
+    <a id="abu_adl_runscript_a" class="ablLink uploadLink" style="display:block" href="" onclick="return false;" data-type="xnat:projectData" data-uri="$content.getURI("/data/projects/$om.getId()")">Run Automation Script</a>
+ </li>
+#end
diff --git a/src/main/webapp/xnat-templates/screens/xnat_projectData/actionsBox/UploadFiles.vm b/src/main/webapp/xnat-templates/screens/xnat_projectData/actionsBox/UploadFiles.vm
new file mode 100644
index 0000000000000000000000000000000000000000..9fa1a3c21f46437377f1e5a9ee9e460f070004e8
--- /dev/null
+++ b/src/main/webapp/xnat-templates/screens/xnat_projectData/actionsBox/UploadFiles.vm
@@ -0,0 +1,17 @@
+#if($om.canEdit($user))
+ <li id="abu_adl_files_li" class="yuimenuitem">
+    <a id="abu_adl_files_a" class="abuLink uploadLink" style="display:block" href="" onclick="return false;" data-type="xnat:projectData" data-uri="$content.getURI("/data/projects/$om.getId()")">Upload Additional Files</a>
+ </li>
+ <script type="text/javascript" src="$content.getURI("scripts/uploaders/AutomationBasedUploader.js")"></script>
+ <script type="text/javascript" src="$content.getURI("scripts/uploaders/fileuploader.js")"></script>
+ <script type="text/javascript" src="$content.getURI("scripts/lib/jquery-plugins/jquery.form.js")"></script>
+ <link type="text/css" rel="stylesheet" href="$content.getURI("style/uploaders/fileuploader.css")">
+#### link to the former custom uploader
+## <li id="cru_adl_files_li" class="yuimenuitem">
+##    <a id="cru_adl_files_a" class="yuimenuitemlabel uploadLink" style="display:none" href="" onclick="return false;" data-type="xnat:projectData" data-uri="$content.getURI("/data/projects/$om.getId()")">Upload Files (Custom)</A>
+##   </li>
+##    <script type="text/javascript" src="$content.getURI("scripts/uploaders/ConfiguredResourceUploader.js")"></script>
+##    <script>
+##		XNAT.app.crConfigs.project='$!om.getId()';
+##    </script>
+#end
diff --git a/src/main/webapp/xnat-templates/screens/xnat_projectData/report/manage_tab/ResourceManagement.vm b/src/main/webapp/xnat-templates/screens/xnat_projectData/report/manage_tab/ResourceManagement.vm
index c75cf1e6f86e7fb5884e496b1794f8d4c7736c94..f7aa07c98d8d9e028514429b5e0a5d6a73c94daf 100644
--- a/src/main/webapp/xnat-templates/screens/xnat_projectData/report/manage_tab/ResourceManagement.vm
+++ b/src/main/webapp/xnat-templates/screens/xnat_projectData/report/manage_tab/ResourceManagement.vm
@@ -2,7 +2,7 @@
 ##Developer: Tim Olsen tim@deck5consulting.com
 ##Section of Manage tab that starts the Project Resource Configuration dialog
 <!-- Sequence: 100 -->
-#if($data.getSession().getAttribute("user").canCreate($project))
+#if($data.getSession().getAttribute("userHelper").canCreate($project))
 <h3>Project Resource Settings</h3>
 <div>
 <table id="project_resource_table">
@@ -180,4 +180,4 @@
 
         </style>
 </div>
-#end
\ No newline at end of file
+#end
diff --git a/src/main/webapp/xnat-templates/screens/xnat_projectData/xnat_projectData_summary_manage.vm b/src/main/webapp/xnat-templates/screens/xnat_projectData/xnat_projectData_summary_manage.vm
index 49c0abaa603f1af1c4dfd56ffbfb1a8e4ab6a27b..5b2a918ef80e104ed0b88485b16ab93e4922788b 100644
--- a/src/main/webapp/xnat-templates/screens/xnat_projectData/xnat_projectData_summary_manage.vm
+++ b/src/main/webapp/xnat-templates/screens/xnat_projectData/xnat_projectData_summary_manage.vm
@@ -369,14 +369,13 @@
             <th>Event</th>
             <th>Script ID</th>
             <th>Description</th>
-            <th></th>
             </thead>
             <tbody>
             ## content populated with XNAT.app.eventsManager.initEventsTable()
             </tbody>
         </table>
         <br>
-        <button type="button" id="add_event_handler" class="btn1" style="font-size:12px;">Add Event Handler</button>
+        <button type="button" id="manage_event_handlers" class="btn1" style="font-size:12px;">Manage Event Handlers</button>
         <div class="alert" style="margin:15px 0;max-width:696px;min-width:400px;">
             Automation is an emerging feature in the XNAT server platform. It allows you to create scripts in a number
             of scripting languages that can be triggered by system events. You should use it only if you or your team
diff --git a/src/main/webapp/xnat-templates/screens/xnat_subjectAssessorData/actionsBox/CustomUploader.vm b/src/main/webapp/xnat-templates/screens/xnat_subjectAssessorData/actionsBox/CustomUploader.vm
deleted file mode 100644
index 40e2f4403f3a65dda0094942f9076ceaaf8c0872..0000000000000000000000000000000000000000
--- a/src/main/webapp/xnat-templates/screens/xnat_subjectAssessorData/actionsBox/CustomUploader.vm
+++ /dev/null
@@ -1,9 +0,0 @@
-#if($om.canEdit($user))
- <li id="cru_adl_files_li" class="yuimenuitem">
-    <a id="cru_adl_files_a" class="yuimenuitemlabel uploadLink" style="display:none" href="" onclick="return false;" data-type="$om.getXSIType()" data-uri="$content.getURI("/data/experiments/$om.getId()")">Upload Additional Files</A>
-   </li>
-    <script type="text/javascript" src="$content.getURI("scripts/uploaders/ConfiguredResourceUploader.js")"></script>
-    <script>
-		XNAT.app.crConfigs.project='$!om.getProject()';
-    </script>
-#end
\ No newline at end of file
diff --git a/src/main/webapp/xnat-templates/screens/xnat_subjectAssessorData/actionsBox/RunScript.vm b/src/main/webapp/xnat-templates/screens/xnat_subjectAssessorData/actionsBox/RunScript.vm
new file mode 100644
index 0000000000000000000000000000000000000000..11994959e42ad915a022887309267cd97b8e0b44
--- /dev/null
+++ b/src/main/webapp/xnat-templates/screens/xnat_subjectAssessorData/actionsBox/RunScript.vm
@@ -0,0 +1,5 @@
+#if($om.canEdit($user))
+ <li id="abu_adl_runscript_li" class="yuimenuitem">
+    <a id="abu_adl_runscript_a" class="ablLink uploadLink" style="display:block" href="" onclick="return false;" data-type="$om.getXSIType()" data-uri="$content.getURI("/data/experiments/$om.getId()")">Run Automation Script</a>
+ </li>
+#end
diff --git a/src/main/webapp/xnat-templates/screens/xnat_subjectAssessorData/actionsBox/UploadFiles.vm b/src/main/webapp/xnat-templates/screens/xnat_subjectAssessorData/actionsBox/UploadFiles.vm
new file mode 100644
index 0000000000000000000000000000000000000000..ad115d00e5266e4efc49e448b5e218a9626f58fd
--- /dev/null
+++ b/src/main/webapp/xnat-templates/screens/xnat_subjectAssessorData/actionsBox/UploadFiles.vm
@@ -0,0 +1,19 @@
+#if($om.canEdit($user))
+ <li id="abu_adl_files_li" class="yuimenuitem">
+    <!--<a id="abu_adl_files_a" class="abuLink uploadLink" style="display:block" href="" onclick="return false;" data-type="$om.getXSIType()" data-uri="$content.getURI("/data/experiments/$om.getId()")">Upload Additional Files</a>-->
+    <a id="abu_adl_files_a" class="abuLink uploadLink" style="display:block" href="" onclick="return false;" data-type="$om.getXSIType()" data-uri="$content.getURI("/data/experiments/$om.getId()")">Upload Additional Files</a>
+ </li>
+ <script type="text/javascript" src="$content.getURI("scripts/uploaders/AutomationBasedUploader.js")"></script>
+ <script type="text/javascript" src="$content.getURI("scripts/uploaders/fileuploader.js")"></script>
+ <script type="text/javascript" src="$content.getURI("scripts/lib/jquery-plugins/jquery.form.js")"></script>
+ <link type="text/css" rel="stylesheet" href="$content.getURI("style/uploaders/fileuploader.css")">
+#### Link to the former custom uploader
+## <li id="cru_adl_files_li" class="yuimenuitem">
+##    <a id="cru_adl_files_a" class="yuimenuitemlabel uploadLink" style="display:none" href="" onclick="return false;" data-type="$om.getXSIType()" data-uri="$content.getURI("/data/experiments/$om.getId()")">Upload Files (Custom)</A>
+##   </li>
+##    <script type="text/javascript" src="$content.getURI("scripts/uploaders/ConfiguredResourceUploader.js")"></script>
+##    <script>
+##		XNAT.app.crConfigs.project='$!om.getProject()';
+##    </script>
+#####
+#end
diff --git a/src/main/webapp/xnat-templates/screens/xnat_subjectData/actionsBox/CustomUploader.vm b/src/main/webapp/xnat-templates/screens/xnat_subjectData/actionsBox/CustomUploader.vm
deleted file mode 100644
index ea7896ad9142755cd2a027c091f47b7a58289910..0000000000000000000000000000000000000000
--- a/src/main/webapp/xnat-templates/screens/xnat_subjectData/actionsBox/CustomUploader.vm
+++ /dev/null
@@ -1,9 +0,0 @@
-#if($om.canEdit($user))
- <li id="cru_adl_files_li" class="yuimenuitem">
-    <a id="cru_adl_files_a" class="yuimenuitemlabel uploadLink" style="display:none" href="" onclick="return false;" data-type="xnat:subjectData" data-uri="$content.getURI("/data/projects/$om.getProject()/subjects/$om.getId()")">Upload Additional Files</A>
-   </li>
-    <script type="text/javascript" src="$content.getURI("scripts/uploaders/ConfiguredResourceUploader.js")"></script>
-    <script>
-		XNAT.app.crConfigs.project='$!om.getProject()';
-    </script>
-#end
\ No newline at end of file
diff --git a/src/main/webapp/xnat-templates/screens/xnat_subjectData/actionsBox/RunScript.vm b/src/main/webapp/xnat-templates/screens/xnat_subjectData/actionsBox/RunScript.vm
new file mode 100644
index 0000000000000000000000000000000000000000..e57c018e18d9170aa20eab3835f151a8b8ba46bf
--- /dev/null
+++ b/src/main/webapp/xnat-templates/screens/xnat_subjectData/actionsBox/RunScript.vm
@@ -0,0 +1,5 @@
+#if($om.canEdit($user))
+ <li id="abu_adl_runscript_li" class="yuimenuitem">
+    <a id="abu_adl_runscript_a" class="ablLink uploadLink" style="display:block" href="" onclick="return false;" data-type="xnat:subjectData" data-uri="$content.getURI("/data/projects/$om.getProject()/subjects/$om.getId()")">Run Automation Script</a>
+ </li>
+#end
diff --git a/src/main/webapp/xnat-templates/screens/xnat_subjectData/actionsBox/UploadFiles.vm b/src/main/webapp/xnat-templates/screens/xnat_subjectData/actionsBox/UploadFiles.vm
new file mode 100644
index 0000000000000000000000000000000000000000..d73c12334c794b2b96746a3b40e0f99b3afdc239
--- /dev/null
+++ b/src/main/webapp/xnat-templates/screens/xnat_subjectData/actionsBox/UploadFiles.vm
@@ -0,0 +1,17 @@
+#if($om.canEdit($user))
+ <li id="abu_adl_files_li" class="yuimenuitem">
+    <a id="abu_adl_files_a" class="abuLink uploadLink" style="display:block" href="" onclick="return false;" data-type="xnat:subjectData" data-uri="$content.getURI("/data/projects/$om.getProject()/subjects/$om.getId()")">Upload Additional Files</a>
+ </li>
+ <script type="text/javascript" src="$content.getURI("scripts/uploaders/AutomationBasedUploader.js")"></script>
+ <script type="text/javascript" src="$content.getURI("scripts/uploaders/fileuploader.js")"></script>
+ <script type="text/javascript" src="$content.getURI("scripts/lib/jquery-plugins/jquery.form.js")"></script>
+ <link type="text/css" rel="stylesheet" href="$content.getURI("style/uploaders/fileuploader.css")">
+#### Link to the former custom uploader
+## <li id="cru_adl_files_li" class="yuimenuitem">
+##    <a id="cru_adl_files_a" class="yuimenuitemlabel uploadLink" style="display:none" href="" onclick="return false;" data-type="xnat:subjectData" data-uri="$content.getURI("/data/projects/$om.getProject()/subjects/$om.getId()")">Upload Files (Custom)</A>
+##   </li>
+##    <script type="text/javascript" src="$content.getURI("scripts/uploaders/ConfiguredResourceUploader.js")"></script>
+##    <script>
+##		XNAT.app.crConfigs.project='$!om.getProject()';
+##    </script>
+#end