diff --git a/src/main/java/org/nrg/xnat/restlet/actions/AutomationBasedImporter.java b/src/main/java/org/nrg/xnat/restlet/actions/AutomationBasedImporter.java index 3d02fb72f854c1b4e99de643fefb8fd953ca5b6f..74de506e3c1e4f7ddd3c3e2c54f5d708472f867a 100644 --- a/src/main/java/org/nrg/xnat/restlet/actions/AutomationBasedImporter.java +++ b/src/main/java/org/nrg/xnat/restlet/actions/AutomationBasedImporter.java @@ -18,6 +18,7 @@ import com.google.gson.Gson; import com.google.gson.JsonParseException; import com.google.gson.reflect.TypeToken; import org.apache.commons.lang.StringEscapeUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import org.json.JSONArray; import org.json.JSONException; @@ -187,8 +188,10 @@ public class AutomationBasedImporter extends ImporterHandlerA implements Callabl final Object configuredResourceParam = params.get("configuredResource"); final Object buildPathParam = params.get("buildPath"); final Object eventHandlerParam = params.get("eventHandler"); + final Object escapeHtmlParam = params.get("escapeHtml"); // final Object sendemailParam = params.get("sendemail"); final boolean doProcess = processParam != null && processParam.toString().equalsIgnoreCase("true"); + final boolean escapeHtml = escapeHtmlParam != null && escapeHtmlParam.toString().equalsIgnoreCase("true"); configuredResource = (configuredResourceParam != null) ? configuredResourceParam.toString() : CACHE_CONSTANT; // Uploads to configured resources are handled on the client side. Only @@ -198,7 +201,7 @@ public class AutomationBasedImporter extends ImporterHandlerA implements Callabl return; } if (doProcess && !configuredResource.equalsIgnoreCase(CACHE_CONSTANT)) { - doAutomation(); + doAutomation(escapeHtml); return; } @@ -239,7 +242,7 @@ public class AutomationBasedImporter extends ImporterHandlerA implements Callabl // Conditionally process cache location files, otherwise return cache // location if (doProcess) { - doAutomation(); + doAutomation(escapeHtml); /* * if (sendemailParam!=null && * sendemailParam.toString().equalsIgnoreCase("true")) { @@ -465,7 +468,7 @@ public class AutomationBasedImporter extends ImporterHandlerA implements Callabl * the server exception */ @SuppressWarnings({ "rawtypes", "static-access" }) - private void doAutomation() throws ClientException, ServerException { + private void doAutomation(boolean escapeHtml) throws ClientException, ServerException { returnList.add("<b>BEGIN PROCESSING UPLOADED FILES</b>"); @@ -663,47 +666,77 @@ public class AutomationBasedImporter extends ImporterHandlerA implements Callabl for (ScriptOutput scriptOut : scriptOutputs) { returnList.add("<br><b>SCRIPT EXECUTION RESULT</b>"); returnList.add("<br><b>FINAL STATUS: " + scriptOut.getStatus() + "</b>"); - StringWriter writer = new StringWriter(); if (scriptOut.getStatus().equals(Status.ERROR) && scriptOut.getResults() != null && scriptOut.getResults().toString().length() > 0) { returnList.add("<br><b>SCRIPT RESULTS</b><br>"); try { - StringEscapeUtils.escapeHtml(writer, scriptOut.getResults().toString()); - returnList.add(writer.toString().replace("\n", "<br>")); - writer.close(); + if (escapeHtml) { + final StringWriter writer = new StringWriter(); + StringEscapeUtils.escapeHtml(writer, scriptOut.getResults().toString()); + returnList.add(writer.toString().replace("\n", "<br>")); + writer.close(); + } else { + returnList.add(conditionallyAddHtmlBreaks(scriptOut.getResults().toString())); + } } catch (IOException e) { - returnList.add(scriptOut.getResults().toString().replace("\n", "<br>")); + returnList.add(conditionallyAddHtmlBreaks(scriptOut.getResults().toString())); } } if (scriptOut.getOutput() != null && scriptOut.getOutput().length() > 0) { returnList.add("<br><b>SCRIPT STDOUT</b><br>"); try { - StringEscapeUtils.escapeHtml(writer, scriptOut.getOutput()); - returnList.add(writer.toString().replace("\n", "<br>")); - writer.close(); + if (escapeHtml) { + final StringWriter writer = new StringWriter(); + StringEscapeUtils.escapeHtml(writer, scriptOut.getOutput()); + returnList.add(writer.toString().replace("\n", "<br>")); + writer.close(); + } else { + returnList.add(conditionallyAddHtmlBreaks(scriptOut.getOutput().toString())); + } } catch (IOException e) { - returnList.add(scriptOut.getOutput().replace("\n", "<br>")); + returnList.add(conditionallyAddHtmlBreaks(scriptOut.getOutput().toString())); } } if (scriptOut.getErrorOutput() != null && scriptOut.getErrorOutput().length() > 0) { returnList.add("<br><b>SCRIPT STDERR/EXCEPTION</b><br>"); try { - StringEscapeUtils.escapeHtml(writer, scriptOut.getErrorOutput()); - returnList.add(writer.toString().replace("\n", "<br>")); - writer.close(); + if (escapeHtml) { + final StringWriter writer = new StringWriter(); + StringEscapeUtils.escapeHtml(writer, scriptOut.getErrorOutput()); + returnList.add(writer.toString().replace("\n", "<br>")); + writer.close(); + } else { + returnList.add(conditionallyAddHtmlBreaks(scriptOut.getErrorOutput().toString())); + } } catch (IOException e) { - returnList.add(scriptOut.getErrorOutput().replace("\n", "<br>")); + returnList.add(conditionallyAddHtmlBreaks(scriptOut.getErrorOutput().toString())); } } } } else { returnList.add("<br><b>No output was returned from the script</b>"); } - returnList.add("<br><b>FINISHED PROCESSING"); + returnList.add("<br><b>PROCESSING COMPLETE"); + } + private String conditionallyAddHtmlBreaks(String output) { + // An (overly) simplistic check just trying to see if the output is doing its own line breaking. Otherwise we'll add them. + // I'm not sure we want to do this, but some currently in used scripts plan on the importer doing the breaks and + // this could be useful for text output that doesn't request the html to be escaped. + return output.replace("\n", ((double)((StringUtils.countMatches(output,"<br>")+ + StringUtils.countMatches(output,"<br/>")+ + StringUtils.countMatches(output,"<p>")+ + StringUtils.countMatches(output,"<tr>")+ + StringUtils.countMatches(output,"<ul>")+ + StringUtils.countMatches(output,"<ul>")+ + StringUtils.countMatches(output,"<ol>")+ + StringUtils.countMatches(output,"<li>")+ + StringUtils.countMatches(output,"<dl>") + ))/((double)(StringUtils.countMatches(output,"\n")+1))>0.95) ? "" : "<br>") + "<br>"; + } - /** + /** * Builds the workflow. * * @param proj the proj 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 ea7ebaded96f2c37432267578e85a47e97bc1625..a3a3efbbb86149296ef8db19666e3259810ad0d8 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 @@ -101,7 +101,7 @@ public class AutomatedScriptRequestListener { (automationCompletionEvent.getScriptOutputs() != null && automationCompletionEvent.getScriptOutputs().size() > 0) ? scriptOutputToHtmlString(automationCompletionEvent.getScriptOutputs()) : "<h3>No output was returned from the script run</h3>"; - final String EMAIL_SUBJECT = "Automation Results"; + final String EMAIL_SUBJECT = "Automation Results (" + request.getScriptId() + ")"; AdminUtils.sendUserHTMLEmail(EMAIL_SUBJECT, scriptOutStr, false, notifyList.toArray(new String[0])); } } diff --git a/src/main/webapp/scripts/uploaders/AutomationBasedUploader.js b/src/main/webapp/scripts/uploaders/AutomationBasedUploader.js index 880f428cc195971c0241d7cf620e9de677cbe4ce..7997da012a0193d6a2930fb9e179ed9ecd9a8960 100644 --- a/src/main/webapp/scripts/uploaders/AutomationBasedUploader.js +++ b/src/main/webapp/scripts/uploaders/AutomationBasedUploader.js @@ -923,17 +923,20 @@ XNAT.app.abu.processFiles=function() { var eventHandlerScope = eventHandlerElement.className; } + this.currentUploaderConfig = undefined; this.paramsToPass = undefined; for (var i=0;i<this.uploaderConfig.length;i++) { - if (this.uploaderConfig[i].eventTriggerId==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; + if (this.uploaderConfig[i].eventTriggerId==eventHandler && this.uploaderConfig[i].eventScope==eventHandlerScope) { + this.currentUploaderConfig = this.uploaderConfig[i]; break; } } + this.paramsToPass = undefined; + if (this.currentUploaderConfig.parameters!=undefined && this.currentUploaderConfig.parameters!=null && this.currentUploaderConfig.parameters.length>0) { + this.paramsToPass = this.currentUploaderConfig.parameters; + } if (typeof(this.paramsToPass) !== 'undefined' && this.paramsToPass != null && this.paramsToPass.length>0) { - var pModalOpts = { width: 740, height: 480, @@ -1003,6 +1006,10 @@ XNAT.app.abu.continueProcessing=function() { if (eventHandler != undefined && eventHandler != null && eventHandler.length>0) { params['eventHandler'] = eventHandler; } + var escapeHtml = (typeof this.currentUploaderConfig !== 'undefined') ? this.currentUploaderConfig.escapeHtml : undefined; + if (escapeHtml != undefined && escapeHtml != null) { + params['escapeHtml'] = escapeHtml; + } params['xsiType'] = XNAT.data.context.xsiType; var queryParams = "?import-handler=" + XNAT.app.abu.importHandler + "&" + $.param(params); @@ -1102,6 +1109,7 @@ XNAT.app.abu.saveUploaderConfiguration=function(configTriggerId, configEvent, sc newConfigObj.launchFromResourceUploads = $('#ULC_RB_launchFromResourceUploads').is(':checked'); newConfigObj.launchWithoutUploads = $('#ULC_RB_launchWithoutUploads').is(':checked'); newConfigObj.doNotUseUploader = $('#ULC_RB_doNotUseUploader').is(':checked'); + newConfigObj.escapeHtml = $('#ULC_RB_outputText').is(':checked'); newConfigObj.parameters = undefined; $(".ULC_parametersDiv").each(function() { var parameterField = $(this).find(".ULC_parametersField").val(); @@ -1244,6 +1252,7 @@ XNAT.app.abu.configureUploaderForEventHandler=function(configTriggerId, configEv configObj.launchFromResourceUploads = false; configObj.launchWithoutUploads = false; configObj.doNotUseUploader = true; + configObj.escapeHtml = false; // best to leave these undefined, I think //configObj.parameters = [ ]; //configObj.contexts = [ 'xnat:projectData','xnat:subjectAssessorData','xnat:imageAssessorData','xnat:imageSessionData','xnat:imageScanData','xnat:subjectData' ]; @@ -1278,19 +1287,28 @@ XNAT.app.abu.configureUploaderForEventHandler=function(configTriggerId, configEv /** 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"' + + configHtml+='<div style="margin-left:20px;width:100%"><input type="radio" id="ULC_RB_launchFromCacheUploads" name="ULC_RB_USAGE" value="launchFromCacheUploads"' + ((configObj.launchFromCacheUploads) ? ' checked' : '') + '> <b> Use for cache space uploads</b> </div>'; if (scope!='site') { - configHtml+='<div style="margin-left:20px;width:100%"><input type="radio" id="ULC_RB_launchFromResourceUploads" name="ULC_RB" value="launchFromResourceUploads"' + + configHtml+='<div style="margin-left:20px;width:100%"><input type="radio" id="ULC_RB_launchFromResourceUploads" name="ULC_RB_USAGE" 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"' + + configHtml+='<div style="margin-left:20px;width:100%"><input type="radio" id="ULC_RB_launchWithoutUploads" name="ULC_RB_USAGE" value="launchWithoutUploads"' + ((configObj.launchWithoutUploads) ? ' checked' : '') + '> <b> Trigger without uploads </b> </div>'; - configHtml+='<div style="margin-left:20px;width:100%"><input type="radio" id="ULC_RB_doNotUseUploader" name="ULC_RB" value="doNotUseUploader"' + + configHtml+='<div style="margin-left:20px;width:100%"><input type="radio" id="ULC_RB_doNotUseUploader" name="ULC_RB_USAGE" value="doNotUseUploader"' + ((configObj.doNotUseUploader) ? ' checked' : '') + '> <b> Do not use uploader </b> </div>'; configHtml+='</div></p>'; configHtml+='<p>'; + configHtml+='<div style="margin-left:20px;width:100%"><p><b>Output Type:</b>'; + configHtml+='<div style="margin-left:20px;width:100%"><input type="radio" id="ULC_RB_outputHtml" name="ULC_RB_OUTPUT" value="outputHtml"' + + ((configObj.escapeHtml) ? '' : 'checked') + '> <b> HTML </b> </div>'; + configHtml+='<div style="margin-left:20px;width:100%"><input type="radio" id="ULC_RB_outputText" name="ULC_RB_OUTPUT" value="outputText"' + + ((configObj.escapeHtml) ? ' checked' : '') + '> <b> Text (escape HTML characters) </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)); diff --git a/src/main/webapp/style/uploaders/fileuploader.css b/src/main/webapp/style/uploaders/fileuploader.css index fe323e24d1c0f126c3b975e2cfaea3ff59b3d952..ac753346bcdf7116040874c536e3604a97af6dce 100644 --- a/src/main/webapp/style/uploaders/fileuploader.css +++ b/src/main/webapp/style/uploaders/fileuploader.css @@ -125,3 +125,6 @@ div.abu-xnat-interactivity-area-sub { .passParamText { margin-left: 30px; margin-right: 10px; } .interactivityAreaSpan { margin-left: 15px; margin-right: 10px; } + +#configUploadDiv { overflow-x: hidden; } +