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&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&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'> </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