diff --git a/build.gradle b/build.gradle index 74c34eaf6d5920a65ce0de8bd5fb0c1d29f0d065..4b09cd3fa576ec7a942091539a1175956596d26a 100644 --- a/build.gradle +++ b/build.gradle @@ -306,6 +306,7 @@ dependencies { compile "org.hibernate:hibernate-core:${vHibernate}" compile "org.hibernate:hibernate-ehcache:${vHibernate}" compile "org.hibernate:hibernate-validator:4.3.2.Final" + compile "org.hibernate:hibernate-envers:4.3.11.Final" compile "net.sf.ehcache:ehcache-core:${vEhcache}" compile "com.noelios.restlet:com.noelios.restlet:1.1.10" @@ -361,6 +362,7 @@ dependencies { compile "log4j:log4j:${vLog4j}" compile "javax.servlet:jstl:1.2" compile "javax.mail:javax.mail-api:1.5.5" + compile 'com.google.code.gson:gson:2.6.2' runtime "org.apache.activemq:activemq-core:5.7.0" runtime "ant:ant:1.6.5" diff --git a/src/main/java/org/nrg/dcm/id/ScriptedSessionAssignmentExtractor.java b/src/main/java/org/nrg/dcm/id/ScriptedSessionAssignmentExtractor.java index 47188bf1ee4c5f86ee5ec6788da2e96be416cbae..b5684b3d91d4cd5ebbe43d9b34e1f8a6788584b8 100644 --- a/src/main/java/org/nrg/dcm/id/ScriptedSessionAssignmentExtractor.java +++ b/src/main/java/org/nrg/dcm/id/ScriptedSessionAssignmentExtractor.java @@ -3,6 +3,7 @@ package org.nrg.dcm.id; import org.apache.commons.lang3.StringUtils; import org.dcm4che2.data.DicomObject; import org.nrg.automation.entities.Script; +import org.nrg.automation.entities.ScriptTrigger; import org.nrg.automation.services.ScriptRunnerService; import org.nrg.dcm.ChainExtractor; import org.nrg.dcm.Extractor; @@ -136,7 +137,8 @@ public class ScriptedSessionAssignmentExtractor extends ChainExtractor implement private Script getScript(final String projectId) { final boolean hasEntity = StringUtils.isNotBlank(projectId); - final Script script = _service.getScript(hasEntity ? Scope.Project : Scope.Site, projectId, _event); + final Script script = _service.getScript(hasEntity ? Scope.Project : Scope.Site, projectId, ScriptTrigger.DEFAULT_CLASS, _event, ScriptTrigger.DEFAULT_FILTER); + // If we didn't find a script for the indicated scope but we have an entity ID, then fail up to the site level. if (script == null && hasEntity) { return getScript(null); diff --git a/src/main/java/org/nrg/xapi/model/event/EventClassInfo.java b/src/main/java/org/nrg/xapi/model/event/EventClassInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..bce9f8ff5ec19b2a80fb661ddbe950804dc9e3b8 --- /dev/null +++ b/src/main/java/org/nrg/xapi/model/event/EventClassInfo.java @@ -0,0 +1,143 @@ +package org.nrg.xapi.model.event; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.collect.Lists; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +import org.nrg.xft.event.EventClass; +import org.python.google.common.collect.Maps; +import org.springframework.core.annotation.AnnotationUtils; + +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +/** + * The Class EventClassInfo. + */ +@ApiModel(description = "Event class names and filterable fields.") +public class EventClassInfo { + + /** The _class name. */ + String _className; + + /** The _display name. */ + String _displayName; + + /** The _filterable fields. */ + final Map<String,List<String>> _filterableFields = Maps.newHashMap(); + + /** The _event ids. */ + final List<String> _eventIds = Lists.newArrayList(); + + /** The _include event ids from database. */ + boolean _includeEventIdsFromDatabase = true; + + /** + * Instantiates a new event class info. + * + * @param className the class name + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public EventClassInfo(final String className) { + this._className = className; + try { + Class clazz = Class.forName(className); + if (clazz.isAnnotationPresent(EventClass.class)) { + + Annotation anno = AnnotationUtils.findAnnotation(clazz, EventClass.class); + Object annoDisplayNameObj = AnnotationUtils.getValue(anno, "displayName"); + _displayName = (annoDisplayNameObj != null && annoDisplayNameObj instanceof String) ? annoDisplayNameObj.toString() : _className; + + Object annoEventIdsObj = AnnotationUtils.getValue(anno, "defaultEventIds"); + String[] annoEventIds = (annoEventIdsObj != null && annoEventIdsObj instanceof String[]) ? (String[])annoEventIdsObj : new String[] {}; + _eventIds.addAll(Arrays.asList(annoEventIds)); + + Object annoIncludeValuesFromDatabase = AnnotationUtils.getValue(anno, "includeValuesFromDatabase"); + if (annoIncludeValuesFromDatabase != null && annoIncludeValuesFromDatabase instanceof Boolean) { + _includeEventIdsFromDatabase = (boolean)annoIncludeValuesFromDatabase; + } + + } else { + _displayName = _className; + } + } catch (ClassNotFoundException e) { + _displayName = _className; + } + } + + /** + * Gets the class name. + * + * @return the class name + */ + @ApiModelProperty(value = "Event class name (Should implement AutomationEventImplementerI") + @JsonProperty("class") + public String getClassName() { + return _className; + } + + /** + * Gets the display name. + * + * @return the display name + */ + @ApiModelProperty(value = "Display Name") + @JsonProperty("displayName") + public String getDisplayName() { + return _displayName; + } + + /** + * Sets the class name. + * + * @param className the new class name + */ + public void setClassName(String className) { + _className = className; + } + + /** + * Gets the filterable fields map. + * + * @return the filterable fields map + */ + @ApiModelProperty(value = "Map of Filterable fields.") + @JsonProperty("filterableFields") + public Map<String,List<String>> getFilterableFieldsMap() { + return _filterableFields; + } + + /** + * Gets the event ids. + * + * @return the event ids + */ + @ApiModelProperty(value = "List of event IDs.") + @JsonProperty("eventIds") + public List<String> getEventIds() { + return _eventIds; + } + + /** + * Gets the include event ids from database. + * + * @return the include event ids from database + */ + public boolean getIncludeEventIdsFromDatabase() { + return _includeEventIdsFromDatabase; + } + + /** + * Sets the include event ids from database. + * + * @param _includeEventIdsFromDatabase the new include event ids from database + */ + public void setIncludeEventIdsFromDatabase(boolean _includeEventIdsFromDatabase) { + this._includeEventIdsFromDatabase = _includeEventIdsFromDatabase; + } + +} diff --git a/src/main/java/org/nrg/xapi/rest/event/EventHandlerApi.java b/src/main/java/org/nrg/xapi/rest/event/EventHandlerApi.java new file mode 100644 index 0000000000000000000000000000000000000000..fc39acce70e9b0f93aac6a4c80e9c02d6e039712 --- /dev/null +++ b/src/main/java/org/nrg/xapi/rest/event/EventHandlerApi.java @@ -0,0 +1,296 @@ +package org.nrg.xapi.rest.event; + +import io.swagger.annotations.*; +import javassist.Modifier; + +import org.nrg.framework.utilities.Reflection; +import org.nrg.xapi.model.event.EventClassInfo; +import org.nrg.xdat.XDAT; +import org.nrg.xdat.om.XnatProjectdata; +import org.nrg.xdat.om.base.auto.AutoXnatProjectdata; +import org.nrg.xdat.security.XDATUser; +import org.nrg.xdat.security.helpers.Roles; +import org.nrg.xdat.services.impl.hibernate.HibernateAutomationEventIdsService; +import org.nrg.xdat.services.impl.hibernate.HibernateAutomationFiltersService; +import org.nrg.xft.event.AutomationEventImplementerI; +import org.nrg.xft.event.Filterable; +import org.nrg.xft.event.entities.AutomationEventIds; +import org.nrg.xft.event.entities.AutomationFilters; +import org.nrg.xft.security.UserI; +import org.nrg.xnat.event.conf.EventPackages; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.bind.annotation.*; + +import com.google.common.collect.Lists; + +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.annotation.PostConstruct; + +/** + * The Class EventHandlerApi. + */ +@Api(description = "The XNAT Event Handler API") +@RestController +public class EventHandlerApi { + + /** The Constant _log. */ + private static final Logger _log = LoggerFactory.getLogger(EventHandlerApi.class); + + /** The event ids service. */ + private HibernateAutomationEventIdsService eventIdsService; + + /** The filters service. */ + private HibernateAutomationFiltersService filtersService; + + /** The event packages. */ + private static EventPackages eventPackages; + + /** + * Inits the this. + */ + @PostConstruct + private void initThis() { + getEventIdsService(); + getFiltersService(); + getEventPackages(); + } + + /** + * Automation event classes get. + * + * @param project_id the project_id + * @return the response entity + */ + @ApiOperation(value = "Get list of event classes.", notes = "Returns a list of classes implementing AutomationEventI.", response = List.class) + @ApiResponses({@ApiResponse(code = 200, message = "An array of class names"), @ApiResponse(code = 500, message = "Unexpected error")}) + @RequestMapping(value = {"/projects/{project_id}/eventHandlers/automationEventClasses"}, produces = {"application/json"}, method = RequestMethod.GET) + @ResponseBody + public ResponseEntity<List<EventClassInfo>> automationEventClassesGet(@PathVariable("project_id") String project_id) { + final HttpStatus status = isPermitted(project_id); + if (status != null) { + return new ResponseEntity<>(status); + } + return new ResponseEntity<List<EventClassInfo>>(getEventInfoList(project_id), HttpStatus.OK); + } + + /** + * Automation event classes get. + * + * @return the response entity + */ + @ApiOperation(value = "Get list of event classes.", notes = "Returns a list of classes implementing AutomationEventI.", response = List.class) + @ApiResponses({@ApiResponse(code = 200, message = "An array of class names"), @ApiResponse(code = 500, message = "Unexpected error")}) + @RequestMapping(value = {"/eventHandlers/automationEventClasses"}, produces = {"application/json"}, method = RequestMethod.GET) + @ResponseBody + public ResponseEntity<List<EventClassInfo>> automationEventClassesGet() { + final HttpStatus status = isPermitted(null); + if (status != null) { + return new ResponseEntity<>(status); + } + return new ResponseEntity<List<EventClassInfo>>(getEventInfoList(null), HttpStatus.OK); + } + + /** + * Gets the event info list. + * + * @param project_id the project_id + * @return the event info list + */ + private List<EventClassInfo> getEventInfoList(String project_id) { + final List<EventClassInfo> eventInfoList = Lists.newArrayList(); + final List<AutomationEventIds> eventIdsList = getEventIdsService().getEventIds(project_id, false); + final List<AutomationFilters> filtersList = getFiltersService().getAutomationFilters(project_id, false); + for (final String className : getEventClassList(eventIdsList)) { + final EventClassInfo eci = new EventClassInfo(className); + final List<String> eventIds = eci.getEventIds(); + final Map<String, List<String>> filterableFields = eci.getFilterableFieldsMap(); + if (eci.getIncludeEventIdsFromDatabase()) { + for (final AutomationEventIds autoIds : eventIdsList) { + if (autoIds.getSrcEventClass().equals(className)) { + for (String eId : autoIds.getEventIds()) { + if (!eventIds.contains(eId)) { + eventIds.add(eId); + } + } + } + } + } + Collections.sort(eventIds); + try { + for (final Method method : Arrays.asList(Class.forName(className).getMethods())) { + if (method.isAnnotationPresent(Filterable.class) && method.getName().substring(0,3).equalsIgnoreCase("get")) { + final char c[] = method.getName().substring(3).toCharArray(); + c[0] = Character.toLowerCase(c[0]); + final String column = new String(c); + final Annotation anno = AnnotationUtils.findAnnotation(method, Filterable.class); + + final Object annoInitialValuesObj = AnnotationUtils.getValue(anno, "initialValues"); + final String[] annoInitialValues = (annoInitialValuesObj != null && annoInitialValuesObj instanceof String[]) ? (String[])annoInitialValuesObj : new String[] {}; + + final Object annoIncludeValuesFromDatabase = AnnotationUtils.getValue(anno, "includeValuesFromDatabase"); + boolean includeValuesFromDatabase = (annoIncludeValuesFromDatabase != null && annoIncludeValuesFromDatabase instanceof Boolean) ? + (boolean)annoIncludeValuesFromDatabase : true; + if (!filterableFields.containsKey(column)) { + final List<String> newValueList = Lists.newArrayList(); + filterableFields.put(column,newValueList); + } + final List<String> valueList = filterableFields.get(column); + valueList.addAll(Arrays.asList(annoInitialValues)); + if (includeValuesFromDatabase) { + for (final AutomationFilters autoFilters : filtersList) { + if (autoFilters.getField().equals(column)) { + valueList.addAll(autoFilters.getValues()); + break; + } + } + } + Collections.sort(valueList); + } + } + } catch (SecurityException | ClassNotFoundException e) { + for (final AutomationFilters autoFilters : filtersList) { + if (!filterableFields.containsKey(autoFilters.getField())) { + final List<String> valueList = Lists.newArrayList(autoFilters.getValues()); + Collections.sort(valueList); + filterableFields.put(autoFilters.getField(), valueList); + } else { + for (String value : autoFilters.getValues()) { + final List<String> values = filterableFields.get(autoFilters.getField()); + if (!values.contains(value)) { + values.add(value); + } + Collections.sort(values); + } + } + } + } + eventInfoList.add(eci); + } + return eventInfoList; + } + + /** + * Gets the event class list. + * + * @param eventIdsList the event ids list + * @return the event class list + */ + private List<String> getEventClassList(List<AutomationEventIds> eventIdsList) { + final List<String> classList = Lists.newArrayList(); + // ClassList should be pulled from available event classes rather than from events + final EventPackages eventPackages = getEventPackages(); + if (eventPackages != null) { + for (final String pkg : eventPackages) { + try { + for (final Class<?> clazz : Reflection.getClassesForPackage(pkg)) { + if (AutomationEventImplementerI.class.isAssignableFrom(clazz) && !clazz.isInterface() && + !Modifier.isAbstract(clazz.getModifiers())) { + if (!classList.contains(clazz.getName())) { + classList.add(clazz.getName()); + } + } + } + } catch (ClassNotFoundException | IOException e) { + // Do nothing. + } + } + } + // I think for now we'll not pull from the database if we've found event classes. If the database + // contains any thing different, it should only be event classes that are no longer available. + if (classList.size()<1) { + for (final AutomationEventIds auto : eventIdsList) { + if (!classList.contains(auto.getSrcEventClass())) { + classList.add(auto.getSrcEventClass()); + } + } + } + return classList; + } + + /** + * Gets the event ids service. + * + * @return the event ids service + */ + private HibernateAutomationEventIdsService getEventIdsService() { + if (eventIdsService == null) { + eventIdsService = XDAT.getContextService().getBean(HibernateAutomationEventIdsService.class); + } + return eventIdsService; + } + + /** + * Gets the filters service. + * + * @return the filters service + */ + private HibernateAutomationFiltersService getFiltersService() { + if (filtersService == null) { + filtersService = XDAT.getContextService().getBean(HibernateAutomationFiltersService.class); + } + return filtersService; + } + + /** + * Gets the event packages. + * + * @return the event packages + */ + private static EventPackages getEventPackages() { + if (eventPackages == null) { + eventPackages = XDAT.getContextService().getBean("eventPackages",EventPackages.class); + } + return eventPackages; + } + + /** + * Gets the session user. + * + * @return the session user + */ + private UserI getSessionUser() { + final Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + if ((principal instanceof UserI)) { + return (UserI) principal; + } + return null; + } + + /** + * Checks if is permitted. + * + * @param project_id the project_id + * @return the http status + */ + private HttpStatus isPermitted(String project_id) { + final UserI sessionUser = getSessionUser(); + if ((sessionUser instanceof XDATUser)) { + if (project_id != null) { + final XnatProjectdata proj = AutoXnatProjectdata.getXnatProjectdatasById(project_id, sessionUser, false); + try { + return (proj.canEdit(sessionUser)) ? null : HttpStatus.FORBIDDEN; + } catch (Exception e) { + _log.error("Error checking read status for project",e); + return HttpStatus.INTERNAL_SERVER_ERROR; + } + } else { + return (Roles.isSiteAdmin(sessionUser)) ? null : HttpStatus.FORBIDDEN; + } + } + _log.error("Error checking read status for project"); + return HttpStatus.INTERNAL_SERVER_ERROR; + } + +} diff --git a/src/main/java/org/nrg/xnat/event/conf/EventPackages.java b/src/main/java/org/nrg/xnat/event/conf/EventPackages.java new file mode 100644 index 0000000000000000000000000000000000000000..966d57c5d379c701de5ac898316a1e745a4bd59f --- /dev/null +++ b/src/main/java/org/nrg/xnat/event/conf/EventPackages.java @@ -0,0 +1,33 @@ +package org.nrg.xnat.event.conf; + +import java.util.HashSet; +import java.util.Set; + +/** + * The Class EventPackages. + */ +public class EventPackages extends HashSet<String> { + + /** The Constant serialVersionUID. */ + private static final long serialVersionUID = 9166262514950558701L; + + /** + * Instantiates a new event packages. + * + * @param packages the packages + */ + public EventPackages(Set<String> packages) { + super(); + this.setPackages(packages); + } + + /** + * Sets the packages. + * + * @param packages the new packages + */ + public void setPackages(Set<String> packages) { + clear(); + addAll(packages); + } +} diff --git a/src/main/java/org/nrg/xnat/event/entities/ScriptLaunchRequestEvent.java b/src/main/java/org/nrg/xnat/event/entities/ScriptLaunchRequestEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..ecaae576417958a5692776c9d3059e3dc8efea1a --- /dev/null +++ b/src/main/java/org/nrg/xnat/event/entities/ScriptLaunchRequestEvent.java @@ -0,0 +1,71 @@ +package org.nrg.xnat.event.entities; + +import java.util.Map; + +import javax.persistence.Entity; +import javax.persistence.PrimaryKeyJoinColumn; +import javax.persistence.Transient; + +import org.nrg.xft.event.AutomationEventImplementerI; +import org.nrg.xft.event.EventClass; +import org.nrg.xft.event.entities.AutomationCompletionEvent; +import org.nrg.xft.event.entities.PersistentEvent; +import org.nrg.xft.event.persist.PersistentEventImplementerI; + +/** + * The Class AutomationLaunchRequestEvent. + */ +@Entity +@PrimaryKeyJoinColumn(name="ID", referencedColumnName="ID") +@EventClass(displayName="Script Launch Request Event") +public class ScriptLaunchRequestEvent extends PersistentEvent implements PersistentEventImplementerI, AutomationEventImplementerI { + + /** The Constant serialVersionUID. */ + private static final long serialVersionUID = 7465778737330635218L; + + /** The automation completion event. */ + private AutomationCompletionEvent automationCompletionEvent; + + private Map<String,Object> parameterMap; + + /** + * Instantiates a new automation launch request event. + */ + public ScriptLaunchRequestEvent() { + super(); + } + + /* (non-Javadoc) + * @see org.nrg.xft.event.AutomationEventImplementerI#getAutomationCompletionEvent() + */ + @Override + @Transient + public AutomationCompletionEvent getAutomationCompletionEvent() { + return automationCompletionEvent; + } + + /* (non-Javadoc) + * @see org.nrg.xft.event.AutomationEventImplementerI#setAutomationCompletionEvent(org.nrg.xft.event.entities.AutomationCompletionEvent) + */ + @Override + public void setAutomationCompletionEvent(AutomationCompletionEvent automationCompletionEvent) { + this.automationCompletionEvent = automationCompletionEvent; + } + + @Override + @Transient + public Map<String, Object> getParameterMap() { + return this.parameterMap; + } + + @Override + public void setParameterMap(Map<String, Object> parameterMap) { + this.parameterMap = parameterMap; + } + + @Override + public void addParameterToParameterMap(String parameter, Object value) { + this.parameterMap.put(parameter, value); + } + +} diff --git a/src/main/java/org/nrg/xnat/event/entities/WorkflowStatusEvent.java b/src/main/java/org/nrg/xnat/event/entities/WorkflowStatusEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..8a56381ebdde492b84393ba62fc46b9a144536ee --- /dev/null +++ b/src/main/java/org/nrg/xnat/event/entities/WorkflowStatusEvent.java @@ -0,0 +1,151 @@ +package org.nrg.xnat.event.entities; + +import java.util.Map; + +import org.nrg.xdat.om.WrkWorkflowdata; +import org.nrg.xft.event.AutomationEventImplementerI; +import org.nrg.xft.event.Filterable; +import org.nrg.xft.event.StructuredEvent; +import org.nrg.xft.event.EventClass; +import org.nrg.xft.event.entities.AutomationCompletionEvent; +import org.nrg.xft.event.persist.PersistentWorkflowI; +import org.nrg.xft.security.UserI; +import org.python.google.common.collect.Maps; + +/** + * The Class WorkflowStatusEvent. + */ +@EventClass(displayName = "Workflow Status Event") +public class WorkflowStatusEvent extends StructuredEvent implements AutomationEventImplementerI { + + /** The Constant serialVersionUID. */ + private static final long serialVersionUID = 7465778737330635218L; + + /** The workflow. */ + PersistentWorkflowI workflow; + + /** The status. */ + private String status; + + /** The justification. */ + private String justification; + + /** The automationCompletionEvent. */ + private AutomationCompletionEvent automationCompletionEvent; + + private Map<String,Object> parameterMap = Maps.newHashMap(); + + /** + * Instantiates a new workflow status event. + */ + public WorkflowStatusEvent() { + super(); + } + + /** + * Instantiates a new workflow status event. u + * + * @param workflow the workflow + */ + public WorkflowStatusEvent(PersistentWorkflowI workflow) { + this(); + this.workflow = workflow; + this.setEventId(workflow.getPipelineName()); + this.setSrcEventClass(this.getClass().getName()); + final String project = workflow.getExternalid(); + this.setExternalId(project); + this.setEntityId(workflow.getId()); + this.setEntityType(workflow.getDataType()); + this.setStatus(workflow.getStatus()); + this.setJustification(workflow.getJustification()); + final Map<String,String> eventSpecificMap = Maps.newHashMap(); + eventSpecificMap.put("status", status); + eventSpecificMap.put("justification", justification); + this.setEventSpecificFieldsAsMap(eventSpecificMap); + final WrkWorkflowdata wrkflow = (workflow instanceof WrkWorkflowdata) ? (WrkWorkflowdata)workflow : null; + if (wrkflow!=null) { + UserI user = (wrkflow.getUser()!=null) ? wrkflow.getUser() : wrkflow.getInsertUser(); + this.setUserId(user.getID()); + } + } + + /** + * Gets the workflow. + * + * @return the workflow + */ + //@Transient + public PersistentWorkflowI getWorkflow() { + return workflow; + } + + /** + * Sets the status. + * + * @param status the new status + */ + public void setStatus(String status) { + this.status = status; + } + + /** + * Gets the status. + * + * @return the status + */ + @Filterable(initialValues = { "Complete", "Failed" }, includeValuesFromDatabase = false) + public String getStatus() { + return status; + } + + /** + * Sets the justification. + * + * @param justification the new justification + */ + public void setJustification(String justification) { + this.justification = justification; + } + + /** + * Gets the justification. + * + * @return the justification + */ + public String getJustification() { + return justification; + } + + /* (non-Javadoc) + * @see org.nrg.xft.event.AutomationEventImplementerI#setAutomationCompletionEvent(org.nrg.xft.event.entities.AutomationCompletionEvent) + */ + @Override + public void setAutomationCompletionEvent(AutomationCompletionEvent automationCompletionEvent) { + this.automationCompletionEvent = automationCompletionEvent; + } + + /* (non-Javadoc) + * @see org.nrg.xft.event.AutomationEventImplementerI#getAutomationCompletionEvent() + */ + @Override + //@Transient + public AutomationCompletionEvent getAutomationCompletionEvent() { + return automationCompletionEvent; + } + + @Override + public Map<String, Object> getParameterMap() { + return this.parameterMap; + } + + @Override + public void setParameterMap(Map<String, Object> parameterMap) { + this.parameterMap = parameterMap; + } + + @Override + public void addParameterToParameterMap(String parameter, Object value) { + this.parameterMap.put(parameter, value); + } + +} diff --git a/src/main/java/org/nrg/xnat/event/listeners/AutoRunEmailHandler.java b/src/main/java/org/nrg/xnat/event/listeners/AutoRunEmailHandler.java index 91e823ce8ae242bcf12b57908861d65475399af6..ca4c48b92e8a6b192e2f2328e1576204dbd5f287 100644 --- a/src/main/java/org/nrg/xnat/event/listeners/AutoRunEmailHandler.java +++ b/src/main/java/org/nrg/xnat/event/listeners/AutoRunEmailHandler.java @@ -7,8 +7,8 @@ import reactor.bus.EventBus; import reactor.fn.Consumer; import org.nrg.xdat.om.WrkWorkflowdata; -import org.nrg.xft.event.WorkflowStatusEvent; import org.nrg.xft.event.persist.PersistentWorkflowUtils; +import org.nrg.xnat.event.entities.WorkflowStatusEvent; import org.springframework.stereotype.Service; import static reactor.bus.selector.Selectors.R; diff --git a/src/main/java/org/nrg/xnat/event/listeners/AutomatedScriptHandler.java b/src/main/java/org/nrg/xnat/event/listeners/AutomatedScriptHandler.java deleted file mode 100755 index 3102d310ef22beeafedc15bad95453788600ab02..0000000000000000000000000000000000000000 --- a/src/main/java/org/nrg/xnat/event/listeners/AutomatedScriptHandler.java +++ /dev/null @@ -1,137 +0,0 @@ -package org.nrg.xnat.event.listeners; - -import com.google.common.collect.Lists; - -import reactor.bus.Event; -import reactor.bus.EventBus; -import reactor.fn.Consumer; - -import org.apache.commons.lang3.StringUtils; -import org.nrg.automation.entities.Script; -import org.nrg.automation.services.ScriptRunnerService; -import org.nrg.framework.constants.Scope; -import org.nrg.xdat.XDAT; -import org.nrg.xdat.om.WrkWorkflowdata; -import org.nrg.xft.ItemI; -import org.nrg.xft.XFTItem; -import org.nrg.xft.collections.ItemCollection; -import org.nrg.xft.event.EventUtils; -import org.nrg.xft.event.WorkflowStatusEvent; -import org.nrg.xft.event.persist.PersistentWorkflowI; -import org.nrg.xft.event.persist.PersistentWorkflowUtils; -import org.nrg.xft.exception.ElementNotFoundException; -import org.nrg.xft.exception.FieldNotFoundException; -import org.nrg.xft.exception.XFTInitException; -import org.nrg.xft.security.UserI; -import org.nrg.xnat.utils.WorkflowUtils; -import org.nrg.xnat.services.messaging.automation.AutomatedScriptRequest; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Service; - -import static reactor.bus.selector.Selectors.$; - -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.Hashtable; -import java.util.List; -import java.util.Set; - -import javax.inject.Inject; - -/** - * The Class AutomatedScriptHandler. - */ -@Service -@SuppressWarnings("unused") -public class AutomatedScriptHandler extends WorkflowStatusEventHandlerAbst implements Consumer<Event<WorkflowStatusEvent>> { - - /** The Constant logger. */ - private static final Logger logger = LoggerFactory.getLogger(AutomatedScriptHandler.class); - @Inject ScriptRunnerService _service; - - /** - * Instantiates a new automated script handler. - * - * @param eventBus the event bus - */ - @Inject public AutomatedScriptHandler( EventBus eventBus ){ - //public AutomatedScriptHandler( EventBus eventBus ){ - eventBus.on($(WorkflowStatusEvent.class.getName() + "." + PersistentWorkflowUtils.COMPLETE), this); - } - - /* (non-Javadoc) - * @see reactor.fn.Consumer#accept(java.lang.Object) - */ - @Override - public void accept(Event<WorkflowStatusEvent> event) { - - final WorkflowStatusEvent wfsEvent = event.getData(); - if (wfsEvent.getWorkflow() instanceof WrkWorkflowdata) { - handleEvent(wfsEvent); - } - - } - - /* (non-Javadoc) - * @see org.nrg.xnat.event.listeners.WorkflowStatusEventHandlerAbst#handleEvent(org.nrg.xft.event.WorkflowStatusEvent) - */ - @Override - public void handleEvent(WorkflowStatusEvent wfsEvent) { - PersistentWorkflowI wrk = wfsEvent.getWorkflow(); - if (!(wrk instanceof WrkWorkflowdata)) { - return; - } - final WrkWorkflowdata wrkflow = (WrkWorkflowdata)wrk; - final UserI user = (wrkflow.getUser()!=null) ? wrkflow.getUser() : wrkflow.getInsertUser(); - final String pipelineName = wrkflow.getPipelineName().replaceAll("\\*OPEN\\*", "(").replaceAll("\\*CLOSE\\*", ")"); - - if (StringUtils.equals(PersistentWorkflowUtils.COMPLETE, wrkflow.getStatus()) && !StringUtils.equals(wrkflow.getExternalid(), PersistentWorkflowUtils.ADMIN_EXTERNAL_ID)) { - //check to see if this has been handled before - for (Script script : getScripts(wrkflow.getExternalid(), pipelineName)) { - try { - final String action = "Executed script " + script.getScriptId(); - final String justification = wrkflow.getJustification(); - final String comment = "Executed script " + script.getScriptId() + " triggered by event " + pipelineName; - PersistentWorkflowI scriptWrk = PersistentWorkflowUtils.buildOpenWorkflow(user, wrkflow.getDataType(), wrkflow.getId(), wrkflow.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()); - AutomatedScriptRequest request = new AutomatedScriptRequest(wrkflow.getWrkWorkflowdataId().toString(), user, script.getScriptId(), pipelineName, scriptWrk.getWorkflowId().toString(), wrk.getDataType(), wrk.getId(), wrk.getExternalid()); - XDAT.sendJmsRequest(request); - } catch (Exception e1) { - logger.error("", e1); - } - } - } - } - - /** - * Gets the scripts. - * - * @param projectId the project id - * @param event the event - * @return the scripts - */ - private List<Script> getScripts(final String projectId, String event) { - - final List<Script> scripts = Lists.newArrayList(); - - //project level scripts - if (StringUtils.isNotBlank(projectId)) { - Script script = _service.getScript(Scope.Project, projectId, event); - if (script != null) { - scripts.add(script); - } - } - - //site level scripts - Script script = _service.getScript(Scope.Site, null, event); - if (script != null) { - scripts.add(script); - } - - return scripts; - } - -} diff --git a/src/main/java/org/nrg/xnat/event/listeners/AutomationCompletionEventListener.java b/src/main/java/org/nrg/xnat/event/listeners/AutomationCompletionEventListener.java new file mode 100644 index 0000000000000000000000000000000000000000..0741edfda1b01193948b2126e25d5be71cfb4be3 --- /dev/null +++ b/src/main/java/org/nrg/xnat/event/listeners/AutomationCompletionEventListener.java @@ -0,0 +1,105 @@ +package org.nrg.xnat.event.listeners; + +import com.google.common.collect.Lists; +import reactor.bus.Event; +import reactor.bus.EventBus; +import reactor.fn.Consumer; +import org.nrg.xdat.XDAT; +import org.nrg.xft.event.entities.AutomationCompletionEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import static reactor.bus.selector.Selectors.type; +import java.util.Iterator; +import java.util.List; +import javax.inject.Inject; + +/** + * The Class AutomatedScriptHandler. + */ +@Service +public class AutomationCompletionEventListener implements Consumer<Event<AutomationCompletionEvent>> { + + /** The Constant logger. */ + private static final Logger logger = LoggerFactory.getLogger(AutomationCompletionEventListener.class); + /** The current instance */ + private static AutomationCompletionEventListener _instance; + /** cache of completed events */ + private List<AutomationCompletionEvent> completedCache = Lists.newArrayList(); + /** HOW LONG WILL WE LET OBJECTS STAY IN CACHE? */ + int CACHE_TIME_MILLIS = 60000; + + /** + * Instantiates a new automation completion event listener. + * + * @throws Exception the exception + */ + public AutomationCompletionEventListener() throws Exception { + if (_instance != null) { + throw new Exception("The AutomationCompletionEventListener service is already initialized. Try calling getService() instead."); + } + _instance = this; + } + + /** + * Instantiates a new automated script handler. + * + * @param eventBus the event bus + * @throws Exception + */ + @Inject public AutomationCompletionEventListener( EventBus eventBus ) throws Exception{ + this(); + eventBus.on(type(AutomationCompletionEvent.class), this); + } + + public static AutomationCompletionEventListener getService() { + if (_instance == null) { + _instance = XDAT.getContextService().getBean(AutomationCompletionEventListener.class); + } + return _instance; + } + + @Override + public void accept(Event<AutomationCompletionEvent> event) { + cleanUpCache(); + if (logger.isDebugEnabled()) { + logger.debug("Receved event " + event.getId() + " - CURRENT TIME: " + System.currentTimeMillis()); + } + if (event.getData().getEventCompletionTime()==null) { + if (logger.isDebugEnabled()) { + logger.debug("WARNING: AutomationCompletionEvent - eventCompletionTime is null"); + } + } + completedCache.add(event.getData()); + } + + private synchronized void cleanUpCache() { + final Iterator<AutomationCompletionEvent> i = completedCache.iterator(); + final long currentTime = System.currentTimeMillis(); + while (i.hasNext()) { + final AutomationCompletionEvent thisEvent = i.next(); + if (thisEvent.getEventCompletionTime()==null || ((currentTime-thisEvent.getEventCompletionTime())>CACHE_TIME_MILLIS)) { + if (logger.isDebugEnabled()) { + logger.debug("cleanUpCache - removed item " + thisEvent.getId() + " - CURRENT TIME: " + currentTime); + } + i.remove(); + } + } + } + + public synchronized AutomationCompletionEvent getEvent(String id) { + final Iterator<AutomationCompletionEvent> i = completedCache.iterator(); + while (i.hasNext()) { + final AutomationCompletionEvent thisEvent = i.next(); + if (thisEvent.getId().equals(id)) { + i.remove(); + return thisEvent; + } + } + if (logger.isDebugEnabled()) { + logger.debug("getEvent - item not found " + id); + } + return null; + } + +} diff --git a/src/main/java/org/nrg/xnat/event/listeners/AutomationEventScriptHandler.java b/src/main/java/org/nrg/xnat/event/listeners/AutomationEventScriptHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..2833b91059f5c495aa45489bb663b0a6a07c3d94 --- /dev/null +++ b/src/main/java/org/nrg/xnat/event/listeners/AutomationEventScriptHandler.java @@ -0,0 +1,420 @@ +package org.nrg.xnat.event.listeners; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import reactor.bus.Event; +import reactor.bus.EventBus; +import reactor.fn.Consumer; +import org.apache.commons.lang3.StringUtils; +import org.nrg.automation.entities.EventFilters; +import org.nrg.automation.entities.Script; +import org.nrg.automation.entities.ScriptOutput; +import org.nrg.automation.entities.ScriptOutput.Status; +import org.nrg.automation.entities.ScriptTrigger; +import org.nrg.automation.services.ScriptRunnerService; +import org.nrg.automation.services.ScriptTriggerService; +import org.nrg.automation.services.impl.hibernate.HibernateScriptTriggerService; +import org.nrg.framework.constants.Scope; +import org.nrg.framework.exceptions.NrgServiceException; +import org.nrg.xdat.XDAT; +import org.nrg.xdat.security.helpers.Users; +import org.nrg.xdat.security.user.exceptions.UserInitException; +import org.nrg.xdat.security.user.exceptions.UserNotFoundException; +import org.nrg.xdat.services.impl.hibernate.HibernateAutomationEventIdsService; +import org.nrg.xdat.services.impl.hibernate.HibernateAutomationFiltersService; +import org.nrg.xdat.services.impl.hibernate.HibernatePersistentEventService; +import org.nrg.xdat.turbine.utils.AdminUtils; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.nrg.xft.event.AutomationEventImplementerI; +import org.nrg.xft.event.EventUtils; +import org.nrg.xft.event.Filterable; +import org.nrg.xft.event.XftEventService; +import org.nrg.xft.event.entities.AutomationCompletionEvent; +import org.nrg.xft.event.entities.AutomationEventIds; +import org.nrg.xft.event.entities.AutomationFilters; +import org.nrg.xft.event.entities.PersistentEvent; +import org.nrg.xft.event.persist.PersistentEventImplementerI; +import org.nrg.xft.event.persist.PersistentWorkflowI; +import org.nrg.xft.event.persist.PersistentWorkflowUtils; +import org.nrg.xft.security.UserI; +import org.nrg.xnat.utils.WorkflowUtils; +import org.nrg.xnat.services.messaging.automation.AutomatedScriptRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import static reactor.bus.selector.Selectors.type; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import javax.annotation.PostConstruct; +import javax.inject.Inject; +import javax.sql.DataSource; + +/** + * The Class AutomatedScriptHandler. + */ +@Service +@SuppressWarnings("unused") +public class AutomationEventScriptHandler implements Consumer<Event<AutomationEventImplementerI>> { + + private String EMAIL_SUBJECT = "Automation Results"; + + /** The Constant logger. */ + private static final Logger logger = LoggerFactory.getLogger(AutomationEventScriptHandler.class); + + /** The _service. */ + @Inject ScriptRunnerService _service; + + /** The _script trigger service. */ + @Inject ScriptTriggerService _scriptTriggerService; + + /** The _data source. */ + @Inject DataSource _dataSource; + + /** + * Instantiates a new automated script handler. + * + * @param eventBus the event bus + */ + @Inject public AutomationEventScriptHandler( EventBus eventBus ){ + eventBus.on(type(AutomationEventImplementerI.class), this); + } + + /** + * init - update xhbm_script_trigger table for XNAT 1.7 + */ + @PostConstruct + public void initUpdateTables() { + /** Update script trigger table for XNAT 1.7. Drop constraints on any columns other than id and trigger_id */ + if (_scriptTriggerService instanceof HibernateScriptTriggerService) { + + List<String> cleanUpQuery = (new JdbcTemplate(_dataSource)).query( + "SELECT DISTINCT 'ALTER TABLE '||tc.table_name||' DROP CONSTRAINT '||tc.constraint_name||';'" + + " FROM information_schema.table_constraints tc " + + " LEFT JOIN information_schema.constraint_column_usage cu " + + " ON cu.constraint_name = tc.constraint_name " + + " WHERE (tc.table_name='xhbm_script_trigger' AND cu.column_name NOT IN ('id', 'trigger_id')) " + , new RowMapper<String>() { + public String mapRow(ResultSet rs, int rowNum) throws SQLException { + return rs.getString(1); + } + }); + if (!cleanUpQuery.isEmpty()) { + logger.info("Cleaning up pre XNAT 1.7 constraints on the xhbm_script_trigger and xhbm_event tables"); + for (String query : cleanUpQuery) { + if (query.contains("xhbm_script_trigger")) { + logger.info("Execute clean-up query (" + query + ")"); + new JdbcTemplate(_dataSource).execute(query); + } + } + } + /** Update table rows for pre-XNAT 1.7 tables to fill in missing column values with defaults */ + ((HibernateScriptTriggerService)_scriptTriggerService).updateOldStyleScriptTriggers(); + } + } + + /* (non-Javadoc) + * @see reactor.fn.Consumer#accept(java.lang.Object) + */ + @Override + public void accept(Event<AutomationEventImplementerI> event) { + + try { + handleAsPersistentEventIfMarkedPersistent(event); + updateAutomationTables(event); + } catch (Throwable t) { + logger.error("Unexpected error persisting Persistent/Automation event information",t); + } finally { + handleEvent(event); + } + + } + + /** + * Update automation tables. + * + * @param event the event + */ + private void updateAutomationTables(Event<AutomationEventImplementerI> event) { + final HibernateAutomationEventIdsService idsService = XDAT.getContextService().getBean(HibernateAutomationEventIdsService.class); + final AutomationEventImplementerI eventData = event.getData(); + if (eventData.getEventId()==null || eventData.getClass()==null) { + return; + } + List<AutomationEventIds> autoIds = idsService.getEventIds(eventData.getExternalId(), eventData.getSrcEventClass(), true); + if (autoIds.size()<1) { + final AutomationEventIds ids = new AutomationEventIds(eventData); + idsService.saveOrUpdate(ids); + } else { + for (final AutomationEventIds ids : autoIds) { + if (!ids.getEventIds().contains(eventData.getEventId())) { + ids.getEventIds().add(eventData.getEventId()); + idsService.saveOrUpdate(ids); + } + } + } + final HibernateAutomationFiltersService filtersService = XDAT.getContextService().getBean(HibernateAutomationFiltersService.class); + final Class<? extends AutomationEventImplementerI> clazz = eventData.getClass(); + for (final Method method : Arrays.asList(clazz.getMethods())) { + if (method.isAnnotationPresent(Filterable.class) && method.getName().substring(0,3).equalsIgnoreCase("get")) { + final char c[] = method.getName().substring(3).toCharArray(); + c[0] = Character.toLowerCase(c[0]); + final String column = new String(c); + AutomationFilters filters = filtersService.getAutomationFilters(eventData.getExternalId(), eventData.getSrcEventClass(), column, true); + if (filters == null) { + filters = new AutomationFilters(eventData, column); + filtersService.saveOrUpdate(filters); + } else { + try { + final String value = method.invoke(eventData).toString(); + final List<String> values = filters.getValues(); + if (!values.contains(value)) { + values.add(value); + filters.setValues(values); + filtersService.saveOrUpdate(filters); + } + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + logger.error("Error invoking method on eventData",e); + } + } + } + } + + } + + /** + * Handle as persistent event if marked persistent. + * + * @param event the event + */ + private void handleAsPersistentEventIfMarkedPersistent(Event<AutomationEventImplementerI> event) { + // Persist the event if this is a PersistentEventImplementerI + if (event.getData() instanceof PersistentEventImplementerI) { + try { + final HibernatePersistentEventService service = XDAT.getContextService().getBean(HibernatePersistentEventService.class); + service.create((PersistentEvent)event.getData()); + } catch (SecurityException | IllegalArgumentException e) { + logger.error("Exception persisting event",e); + } + } + } + + /** + * Handle event. + * + * @param event the event + */ + /* (non-Javadoc) + * @see org.nrg.xnat.event.listeners.WorkflowStatusEventHandlerAbst#handleEvent(org.nrg.xft.event.WorkflowStatusEvent) + */ + public void handleEvent(Event<AutomationEventImplementerI> event) { + final AutomationEventImplementerI automationEvent = event.getData(); + if (automationEvent == null) { + logger.debug("Automation script will not be launched because applicationEvent object is null"); + return; + } + final UserI user; + try { + user = Users.getUser(automationEvent.getUserId()); + } catch (UserNotFoundException | UserInitException e) { + // User is required to launch script + logger.debug("Automation not launching because user object is null"); + return; + } + final String eventClass = automationEvent.getSrcEventClass(); + if (eventClass == null) { + logger.debug("Automation not launching because eventClass is null"); + return; + } + final String eventID = automationEvent.getEventId(); + if (eventID == null) { + logger.debug("Automation not launching because eventID is null"); + return; + } + final Map<String,String> filterMap = Maps.newHashMap(); + final Class<? extends AutomationEventImplementerI> clazz = automationEvent.getClass(); + for (final Method method : Arrays.asList(clazz.getMethods())) { + if (method.isAnnotationPresent(Filterable.class) && method.getName().substring(0,3).equalsIgnoreCase("get")) { + final char c[] = method.getName().substring(3).toCharArray(); + c[0] = Character.toLowerCase(c[0]); + final String column = new String(c); + String value; + try { + final Object rtValue = method.invoke(automationEvent); + if (rtValue != null) { + value = rtValue.toString(); + filterMap.put(column, value); + } + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + logger.error("ERROR calling method on filterable field in event object", e); + // Let's let this pass for now. + } + } + } + if (eventID == null) { + logger.debug("Automation not launching because eventID is null"); + return; + } + final String eventName = eventID.replaceAll("\\*OPEN\\*", "(").replaceAll("\\*CLOSE\\*", ")"); + //check to see if this has been handled before + final AutomationCompletionEvent automationCompletionEvent = automationEvent.getAutomationCompletionEvent(); + for (final Script script : getScripts(automationEvent.getExternalId(), eventClass, eventID, filterMap)) { + try { + final String action = "Executed script " + script.getScriptId(); + Method justMethod = null; + try { + justMethod = automationEvent.getClass().getMethod("getJustification"); + } catch (NoSuchMethodException | NullPointerException | SecurityException e) { + // Do nothing for now + } + final Object justObject = (justMethod!=null) ? justMethod.invoke(automationEvent) : null; + final String justification = (justObject!=null) ? justObject.toString() : ""; + final String comment = "Executed script " + script.getScriptId() + " triggered by event " + eventID; + final PersistentWorkflowI scriptWrk = PersistentWorkflowUtils.buildOpenWorkflow(user, automationEvent.getEntityType(), automationEvent.getEntityId(), automationEvent.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(automationEvent.getSrcStringifiedId(), automationEvent.getSrcEventClass(), user, script.getScriptId(), eventName, + scriptWrk.getWorkflowId().toString(), automationEvent.getEntityType(), automationEvent.getSrcStringifiedId(), automationEvent.getExternalId(), automationEvent.getParameterMap()); + + // We're running this here now, so we can return script output + //XDAT.sendJmsRequest(request); + final ScriptOutput scriptOut = executeScriptRequest(request); + if (automationCompletionEvent != null && scriptOut != null) { + automationCompletionEvent.getScriptOutputs().add(scriptOut); + } + } catch (Exception e1) { + logger.error("Script launch exception", e1); + } + } + if (automationCompletionEvent!= null && automationCompletionEvent.getScriptOutputs().size()>0) { + XftEventService eventService = XftEventService.getService(); + if (eventService != null) { + automationCompletionEvent.setEventCompletionTime(System.currentTimeMillis()); + eventService.triggerEvent(automationCompletionEvent); + List<String> notifyList = automationCompletionEvent.getNotificationList(); + if (notifyList != null && !notifyList.isEmpty()) { + AdminUtils.sendUserHTMLEmail(EMAIL_SUBJECT, scriptOutputToHtmlString(automationCompletionEvent.getScriptOutputs()), false, notifyList.toArray(new String[0])); + } + } + } + } + + private String scriptOutputToHtmlString(List<ScriptOutput> scriptOutputs) { + if (scriptOutputs == null) { + return ""; + } + StringBuilder sb = new StringBuilder(); + for (ScriptOutput scriptOut : scriptOutputs) { + sb.append("<br><b>SCRIPT EXECUTION RESULTS</b><br>"); + sb.append("<br><b>FINAL STATUS: " + scriptOut.getStatus() + "</b><br>"); + if (scriptOut.getStatus().equals(Status.ERROR) && scriptOut.getResults()!=null && scriptOut.getResults().toString().length()>0) { + sb.append("<br><b>SCRIPT RESULTS</b><br>"); + sb.append(scriptOut.getResults().toString().replace("\n", "<br>")); + } + if (scriptOut.getOutput()!=null && scriptOut.getOutput().length()>0) { + sb.append("<br><b>SCRIPT STDOUT</b><br>"); + sb.append(scriptOut.getOutput().replace("\n", "<br>")); + } + if (scriptOut.getErrorOutput()!=null && scriptOut.getErrorOutput().length()>0) { + sb.append("<br><b>SCRIPT STDERR/EXCEPTION</b><br>"); + sb.append(scriptOut.getErrorOutput().replace("\n", "<br>")); + } + } + return sb.toString(); + } + + /** + * Gets the scripts. + * + * @param projectId the project id + * @param eventClass the event class + * @param event the event + * @param filterMap the filter map + * @return the scripts + */ + private List<Script> getScripts(final String projectId, String eventClass, String event, Map<String,String> filterMap) { + + final List<Script> scripts = Lists.newArrayList(); + + //project level scripts + if (StringUtils.isNotBlank(projectId)) { + + final Script script = _service.getScript(Scope.Project, projectId, eventClass, event, filterMap); + if (script != null) { + scripts.add(script); + } + } + + //site level scripts + final Script script = _service.getScript(Scope.Site, null, eventClass, event, filterMap); + if (script != null) { + scripts.add(script); + } + + return scripts; + } + + /** + * Execute script request. + * + * @param request the request + * @return the script output + * @throws Exception the exception + */ + 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 = Maps.newHashMap(); + parameters.put("user", request.getUser()); + parameters.put("scriptId", request.getScriptId()); + parameters.put("event", request.getEvent()); + parameters.put("srcEventId", request.getSrcEventId()); + parameters.put("srcEventClass", request.getSrcEventClass()); + parameters.put("srcWorkflowId", request.getSrcEventId()); + parameters.put("scriptWorkflowId", request.getScriptWorkflowId()); + parameters.put("dataType", request.getDataType()); + parameters.put("dataId", request.getDataId()); + parameters.put("externalId", request.getExternalId()); + parameters.put("workflow", workflow); + if (request.getArgumentMap()!=null && !request.getArgumentMap().isEmpty()) { + parameters.putAll(request.getArgumentMap()); + } + + 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; + } + +} diff --git a/src/main/java/org/nrg/xnat/event/listeners/DicomToNiftiEmailHandler.java b/src/main/java/org/nrg/xnat/event/listeners/DicomToNiftiEmailHandler.java index d316c329a06c72240e096935fc1756cece9d113f..a2c41276fe0f11e76d261694e9403ec1c0c4755f 100644 --- a/src/main/java/org/nrg/xnat/event/listeners/DicomToNiftiEmailHandler.java +++ b/src/main/java/org/nrg/xnat/event/listeners/DicomToNiftiEmailHandler.java @@ -8,8 +8,8 @@ import reactor.fn.Consumer; import org.apache.log4j.Logger; import org.nrg.xdat.om.WrkWorkflowdata; -import org.nrg.xft.event.WorkflowStatusEvent; import org.nrg.xft.event.persist.PersistentWorkflowUtils; +import org.nrg.xnat.event.entities.WorkflowStatusEvent; import org.springframework.stereotype.Service; import static reactor.bus.selector.Selectors.R; diff --git a/src/main/java/org/nrg/xnat/event/listeners/PipelineEmailHandlerAbst.java b/src/main/java/org/nrg/xnat/event/listeners/PipelineEmailHandlerAbst.java index 30690dbe06bfbd07dd4586d768681d359639e023..6468c88d7d4fd70327a7e7a6da281276d34bf5fc 100644 --- a/src/main/java/org/nrg/xnat/event/listeners/PipelineEmailHandlerAbst.java +++ b/src/main/java/org/nrg/xnat/event/listeners/PipelineEmailHandlerAbst.java @@ -18,7 +18,7 @@ import org.nrg.xdat.om.XnatSubjectassessordata; import org.nrg.xdat.schema.SchemaElement; import org.nrg.xdat.turbine.utils.TurbineUtils; import org.nrg.xft.db.PoolDBUtils; -import org.nrg.xft.event.WorkflowStatusEvent; +import org.nrg.xnat.event.entities.WorkflowStatusEvent; import org.nrg.xnat.notifications.NotifyProjectPipelineListeners; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/org/nrg/xnat/event/listeners/WorkflowStatusEventHandlerAbst.java b/src/main/java/org/nrg/xnat/event/listeners/WorkflowStatusEventHandlerAbst.java index fddc98c029f19da14506166c064b260c0ce4f8e7..f74e60e0c8ae99d26dcb0bf84a0fe53458324b87 100644 --- a/src/main/java/org/nrg/xnat/event/listeners/WorkflowStatusEventHandlerAbst.java +++ b/src/main/java/org/nrg/xnat/event/listeners/WorkflowStatusEventHandlerAbst.java @@ -12,8 +12,8 @@ package org.nrg.xnat.event.listeners; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; -import org.nrg.xft.event.WorkflowStatusEvent; import org.nrg.xft.event.persist.PersistentWorkflowUtils; +import org.nrg.xnat.event.entities.WorkflowStatusEvent; /** * @author Tim Olsen <tim@deck5consulting.com> @@ -66,4 +66,4 @@ public abstract class WorkflowStatusEventHandlerAbst { return StringUtils.equals(e.getWorkflow().getStatus(),PersistentWorkflowUtils.COMPLETE); } -} \ No newline at end of file +} diff --git a/src/main/java/org/nrg/xnat/initialization/RootConfig.java b/src/main/java/org/nrg/xnat/initialization/RootConfig.java index 6ae818e422bad777c60226fdf8bef428a53179b9..d8d23ebb241e4426dc01385b351f2ceeb6bf16a2 100644 --- a/src/main/java/org/nrg/xnat/initialization/RootConfig.java +++ b/src/main/java/org/nrg/xnat/initialization/RootConfig.java @@ -7,6 +7,7 @@ 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.event.conf.EventPackages; import org.nrg.xnat.restlet.XnatRestletExtensions; import org.nrg.xnat.restlet.actions.importer.ImporterHandlerPackages; import org.springframework.beans.factory.annotation.Value; @@ -39,8 +40,8 @@ import java.util.List; @ImportResource({"WEB-INF/conf/xnat-security.xml", "WEB-INF/conf/mq-context.xml"}) public class RootConfig { - private static final List<String> DEFAULT_ENTITY_PACKAGES = Arrays.asList("org.nrg.framework.datacache", "org.nrg.xft.entities", "org.nrg.xdat.entities", - "org.nrg.xnat.entities", "org.nrg.prefs.entities", "org.nrg.config.entities"); + public static final List<String> DEFAULT_ENTITY_PACKAGES = Arrays.asList("org.nrg.framework.datacache", "org.nrg.xft.entities", "org.nrg.xft.event.entities", "org.nrg.xdat.entities", + "org.nrg.xnat.entities", "org.nrg.xnat.event.entities", "org.nrg.prefs.entities", "org.nrg.config.entities"); @Bean public String siteId() { @@ -94,6 +95,12 @@ public class RootConfig { return new ImporterHandlerPackages(new HashSet<String>(Arrays.asList(new String[] {"org.nrg.xnat.restlet.actions","org.nrg.xnat.archive"}))); } + @Bean + public EventPackages eventPackages() { + // NOTE: These should be treated as parent packages. All sub-packages should be searched + return new EventPackages(new HashSet<String>(Arrays.asList(new String[] {"org.nrg.xnat.event","org.nrg.xft.event","org.nrg.xdat.event"}))); + } + @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 index 3d3cda9a1476a6028808b6d04a45de0fb361b7a8..1bcbc4b25bf3fbaadf3d99377864e64df9f0e752 100755 --- a/src/main/java/org/nrg/xnat/restlet/XNATApplication.java +++ b/src/main/java/org/nrg/xnat/restlet/XNATApplication.java @@ -339,7 +339,6 @@ public class XNATApplication extends Application { attachURIs(router, ScriptRunnerResource.class, "/automation/runners", "/automation/runners/{LANGUAGE}", "/automation/runners/{LANGUAGE}/{VERSION}"); attachURIs(router, ScriptResource.class, "/automation/scripts", "/automation/scripts/{SCRIPT_ID}", "/automation/scripts/{SCRIPT_ID}/{VERSION}"); attachURIs(router, ScriptVersionsResource.class, "/automation/scriptVersions", "/automation/scriptVersions/{SCRIPT_ID}"); - attachURIs(router, EventResource.class, "/automation/events", "/automation/events/{EVENT_ID}"); attachURIs(router, WorkflowEventResource.class, "/automation/workflows", "/automation/workflows/{SPEC}"); attachURIs(router, ScriptTriggerResource.class, "/automation/handlers", "/automation/handlers/{EVENT_ID}", 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 0cd57f8add0be061f25e7782780a419f84284954..ee9700a20a4efe9a82ef1c08cafac816ec8f8d89 100644 --- a/src/main/java/org/nrg/xnat/restlet/actions/AutomationBasedImporter.java +++ b/src/main/java/org/nrg/xnat/restlet/actions/AutomationBasedImporter.java @@ -12,57 +12,64 @@ package org.nrg.xnat.restlet.actions; */ import java.io.File; +import java.io.IOException; +import java.io.StringWriter; import java.io.UnsupportedEncodingException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Type; 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 com.google.gson.Gson; +import com.google.gson.JsonParseException; +import com.google.gson.reflect.TypeToken; import java.util.concurrent.Callable; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.commons.lang.StringEscapeUtils; import org.apache.log4j.Logger; +import org.json.JSONObject; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONTokener; + 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.automation.entities.ScriptTrigger; +import org.nrg.automation.services.ScriptTriggerService; 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.event.entities.WorkflowStatusEvent; +import org.nrg.xnat.event.listeners.AutomationCompletionEventListener; 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.AutomationEventImplementerI; 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.XftEventService; +import org.nrg.xft.event.Filterable; +import org.nrg.xft.event.entities.AutomationCompletionEvent; import org.nrg.xft.event.persist.PersistentWorkflowI; import org.nrg.xft.event.persist.PersistentWorkflowUtils; import org.nrg.xft.event.persist.PersistentWorkflowUtils.EventRequirementAbsent; @@ -71,50 +78,81 @@ 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> + * The Class AutomationBasedImporter. * + * @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"}; + /** The Constant ZIP_EXT. */ + static final String[] ZIP_EXT = { ".zip", ".jar", ".rar", ".ear", ".gar", ".xar" }; + private static final String STATUS_COMPLETE = "Complete"; + + /** The Constant CACHE_CONSTANT. */ private static final String CACHE_CONSTANT = "_CACHE_"; + + /** The Constant CONFIG_TOOL. */ private static final String CONFIG_TOOL = "resource_config"; + + /** The Constant CONFIG_SCRIPT_PATH. */ private static final String CONFIG_SCRIPT_PATH = "script"; - private static final String EMAIL_SUBJECT = "AutomationBasedImporter results"; + /** The Constant EMAIL_SUBJECT. */ + // private static final String EMAIL_SUBJECT = "AutomationBasedImporter + // results"; + + /** The Constant TIMEOUT_SECONDS. */ + private static final int TIMEOUT_SECONDS = 900; + + /** The logger. */ static Logger logger = Logger.getLogger(AutomationBasedImporter.class); - + + /** The fw. */ private final FileWriterWrapperI fw; + + /** The user. */ private final UserI user; - final Map<String,Object> params; + + /** The params. */ + final Map<String, Object> params; + + /** The return list. */ 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; - + + /** The configured resource. */ + private String configuredResource; + + /** The send admin email. */ + // Is this useful? Do we want it to be configurable? + // private boolean sendAdminEmail = false; + /** - * + * Instantiates a new automation based importer. + * * @param listenerControl + * the listener control * @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) + * the u + * @param fw + * the fw + * @param params + * the params */ 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; + this.user = u; + this.fw = fw; + this.params = params; } + /* + * (non-Javadoc) + * + * @see org.nrg.xnat.restlet.actions.importer.ImporterHandlerA#call() + */ @SuppressWarnings("deprecation") @Override public List<String> call() throws ClientException, ServerException { @@ -123,150 +161,317 @@ public class AutomationBasedImporter extends ImporterHandlerA implements Callabl this.completed("Success"); return returnList; } catch (ClientException e) { - logger.error("",e); + logger.error("", e); this.failed(e.getMessage()); throw e; } catch (ServerException e) { - logger.error("",e); + logger.error("", e); this.failed(e.getMessage()); throw e; } catch (Throwable e) { - logger.error("",e); - throw new ServerException(e.getMessage(),new Exception()); + 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()); - } - */ + /** + * Process upload. + * + * @throws ClientException + * the client exception + * @throws ServerException + * the server exception + */ + private void processUpload() throws ClientException, ServerException { - 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 + final Object eventHandlerParam = params.get("eventHandler"); + // 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) && eventHandlerParam == null) { + createWorkflowEntry(configuredResourceParam.toString()); + return; + } 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 + + // 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 specPath = null; String buildPath = null; - if (buildPathParam!=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; + 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)) { + } 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 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) { + if (buildPath != null) { cacheLoc = new File(buildPath); cacheLoc.mkdirs(); } - + // If uploading a file, process it. - if (fw!=null && cacheLoc!=null) { + if (fw != null && cacheLoc != null) { processFile(cacheLoc, specPath); - } - - // Conditionally process cache location files, otherwise return cache location + } + + // 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) { + /* + * 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 list to html string. + * + * @return the string + */ + /* + * private String returnListToHtmlString() { final StringBuilder sb = new + * StringBuilder("<br/>"); for (final String s : returnList) { + * sb.append(s).append("<br/>\t"); } return sb.toString(); } + */ + + /** + * Send user email. + * + * @param ccAdmin + * the cc admin + */ + /* + * private void sendUserEmail(boolean ccAdmin) { + * AdminUtils.sendUserHTMLEmail(EMAIL_SUBJECT, returnListToHtmlString(), + * ccAdmin, new String[] { user.getEmail() }); } + */ + + /** + * Send admin email. + */ + /* + * private void sendAdminEmail() { + * AdminUtils.sendAdminEmail(user,EMAIL_SUBJECT, returnListToHtmlString()); + * } + */ + + private void createWorkflowEntry(String string) { + + final Map<String, Object> passMap = Maps.newHashMap(); + final Object projectParam = params.get("project"); + final Object subjectParam = params.get("subject"); + final Object experimentParam = params.get("experiment"); + final Object passedParametersParam = params.get("passedParameters"); + String eventText = null; + + @SuppressWarnings("rawtypes") + Class clazz; + Object eventObj; + String eventClass = WorkflowStatusEvent.class.getName(); + try { + clazz = WorkflowStatusEvent.class; + eventObj = clazz.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + returnList.add("ERROR: Could not instantiate event (" + eventClass + "). <br>" + e.toString()); + return; } - 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()); + if (!(eventObj instanceof AutomationEventImplementerI)) { + returnList.add("ERROR: Event (" + eventClass + ") is not an AutomationEventImplementer."); + } + final AutomationEventImplementerI automationEvent = (AutomationEventImplementerI) eventObj; + final String entityId; + final String entityType; + if (experimentParam == null && subjectParam == null) { + entityId = projectParam.toString(); + entityType = XnatProjectdata.SCHEMA_ELEMENT_NAME; + } else if (experimentParam == null && subjectParam != null) { + entityId = subjectParam.toString(); + entityType = XnatSubjectdata.SCHEMA_ELEMENT_NAME; + } else if (experimentParam != null) { + entityId = experimentParam.toString(); + final XnatExperimentdata experimentData = XnatExperimentdata.getXnatExperimentdatasById(experimentParam, + user, false); + entityType = experimentData.getXSIType(); + } else { + entityId = null; + entityType = null; + } + if (entityId == null) { + returnList.add("ERROR: Entity type could not be determined"); + return; + } + automationEvent.setEntityId(entityId); + automationEvent.setEntityType(entityType); + automationEvent.setExternalId(projectParam.toString()); + automationEvent.setSrcEventClass(eventClass); + automationEvent.setUserId(user.getID()); + automationEvent.setEventId(null); + // Create parameter map + 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) { + String passedParametersJsonStr = null; + try { + passedParametersJsonStr = URLDecoder.decode(passedParametersParam.toString(), "UTF-8"); + } catch (UnsupportedEncodingException e1) { + returnList.add("WARNING: Could not parse passed parameters"); + } + if (passedParametersJsonStr != null) { + try { + Type type = new TypeToken<Map<String, String>>() { + }.getType(); + Map<String, String> gsonMap = new Gson().fromJson(passedParametersJsonStr, type); + passMap.putAll(gsonMap); + } catch (JsonParseException e) { + returnList.add("WARNING: Could not parse passed parameters"); + } + } + } + if (!(configuredResource == null || configuredResource.equalsIgnoreCase(CACHE_CONSTANT))) { + eventText = getEventTextFromConfiguredResourceConfig(proj, subj, exp, configuredResource); + passMap.put("configuredResource", configuredResource); + + } + if (eventText!=null) { + buildWorkflow(proj,subj,exp,passMap,eventText); + } + } - private void processFile(final File cacheLoc,final String specPath) throws ClientException { + /** + * Process file. + * + * @param cacheLoc + * the cache loc + * @param specPath + * the spec path + * @throws ClientException + * the client exception + */ + private void processFile(final File cacheLoc, final String specPath) throws ClientException { final String fileName; try { - fileName = URLDecoder.decode(fw.getName(),"UTF-8"); + fileName = URLDecoder.decode(fw.getName(), "UTF-8"); } catch (UnsupportedEncodingException e1) { - throw new ClientException("Could not decode file name.",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)) { + if (extractParam != null && extractParam.toString().equalsIgnoreCase("true") && isZipFileName(fileName)) { final ZipI zipper = getZipper(fileName); try { - zipper.extract(fw.getInputStream(),cacheLoc.getAbsolutePath()); + 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); + 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. + // 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); + throw new ClientException("Could not write uploaded file.", e); } } } - + + /** + * Removes the and throw exception if directory. + * + * @param file + * the file + * @throws ClientException + * the client exception + */ 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."); - }; + throw new ClientException( + "Upload of directories is not currently supported. To upload a directory, please create a zip archive."); + } } } + /** + * Do automation. + * + * @throws ClientException + * the client exception + * @throws ServerException + * the server exception + */ + @SuppressWarnings({ "rawtypes", "static-access" }) private void doAutomation() throws ClientException, ServerException { - - returnList.add("<b>BEGIN PROCESSING UPLOADED FILES</b><br>"); - - final Map<String,Object> passMap = Maps.newHashMap(); + + returnList.add("<b>BEGIN PROCESSING UPLOADED FILES</b>"); + + 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"); @@ -274,86 +479,236 @@ public class AutomationBasedImporter extends ImporterHandlerA implements Callabl final Object buildPathParam = params.get("buildPath"); final Object passedParametersParam = params.get("passedParameters"); String eventText = null; + + final ScriptTriggerService scriptTriggerService = XDAT.getContextService().getBean(ScriptTriggerService.class); + if (scriptTriggerService == null) { + returnList.add("ERROR: Could not obtain event handler service."); + return; + } + final ScriptTrigger scriptTrigger = scriptTriggerService.getByTriggerId(eventHandlerParam.toString()); + if (scriptTrigger == null) { + returnList.add("ERROR: Could not obtain event handler."); + return; + } + final String eventClass = scriptTrigger.getSrcEventClass(); + final Map<String, List<String>> eventFilters = scriptTrigger.getEventFiltersAsMap(); + Class clazz; + Object eventObj; + try { + clazz = Class.forName(eventClass); + eventObj = clazz.newInstance(); + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { + returnList.add("ERROR: Could not instantiate event (" + eventClass + "). <br>" + e.toString()); + return; + } + if (!(eventObj instanceof AutomationEventImplementerI)) { + returnList.add("ERROR: Event (" + eventClass + ") is not an AutomationEventImplementer."); + } + final AutomationEventImplementerI automationEvent = (AutomationEventImplementerI) eventObj; + final String entityId; + final String entityType; + if (experimentParam == null && subjectParam == null) { + entityId = projectParam.toString(); + entityType = XnatProjectdata.SCHEMA_ELEMENT_NAME; + } else if (experimentParam == null && subjectParam != null) { + entityId = subjectParam.toString(); + entityType = XnatSubjectdata.SCHEMA_ELEMENT_NAME; + } else if (experimentParam != null) { + entityId = experimentParam.toString(); + final XnatExperimentdata experimentData = XnatExperimentdata.getXnatExperimentdatasById(experimentParam, + user, false); + entityType = experimentData.SCHEMA_ELEMENT_NAME; + } else { + entityId = null; + entityType = null; + } + if (entityId == null) { + returnList.add("ERROR: Entity type could not be determined"); + return; + } + automationEvent.setEntityId(entityId); + automationEvent.setEntityType(entityType); + automationEvent.setExternalId(projectParam.toString()); + automationEvent.setSrcEventClass(eventClass); + automationEvent.setUserId(user.getID()); + automationEvent.setEventId(scriptTrigger.getEvent()); + final List<Method> setters = Lists.newArrayList(); + for (final Method method : Arrays.asList(automationEvent.getClass().getMethods())) { + if (method.isAnnotationPresent(Filterable.class) + && method.getName().substring(0, 3).equalsIgnoreCase("get")) { + final char c[] = method.getName().substring(3).toCharArray(); + c[0] = Character.toLowerCase(c[0]); + final String column = new String(c); + for (final String filterKey : eventFilters.keySet()) { + if (filterKey.equals(column)) { + Method setter; + try { + setter = automationEvent.getClass().getMethod(method.getName().replaceFirst("get", "set"), + String.class); + } catch (NoSuchMethodException | SecurityException e) { + continue; + } + if (setter.getName().substring(0, 3).equals("set")) { + List<String> filterList = eventFilters.get(filterKey); + setters.add(setter); + if (filterList != null && filterList.size() > 0) { + try { + setter.invoke(automationEvent, filterList.get(0)); + } catch (IllegalAccessException | IllegalArgumentException + | InvocationTargetException e) { + returnList.add("ERROR: Could not set values for filters"); + return; + } + } + } + } + } + + } + } + if (!(setters.size() >= eventFilters.keySet().size())) { + returnList.add("ERROR: Could not set values for filters"); + return; + } + // Create parameter map XnatProjectdata proj = null; XnatSubjectdata subj = null; XnatExperimentdata exp = null; - if (projectParam!=null && projectParam.toString().length()>0) { + 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) { + 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) { + subj = XnatSubjectdata.GetSubjectByProjectIdentifier(proj.getId(), subjectParam.toString(), user, + false); } - if (subj != null) { + if (subj != null) { passMap.put("subject", subj.getId()); } } - if (experimentParam!=null && experimentParam.toString().length()>0) { + 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); + exp = XnatExperimentdata.GetExptByProjectIdentifier(proj.getId(), experimentParam.toString(), user, + false); } if (exp != null) { passMap.put("experiment", exp.getId()); } } - if (passedParametersParam!=null && passedParametersParam.toString().length()>0) { + if (passedParametersParam != null && passedParametersParam.toString().length() > 0) { + String passedParametersJsonStr = null; 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. + passedParametersJsonStr = URLDecoder.decode(passedParametersParam.toString(), "UTF-8"); + } catch (UnsupportedEncodingException e1) { + returnList.add("WARNING: Could not parse passed parameters"); + } + if (passedParametersJsonStr != null) { + try { + Type type = new TypeToken<Map<String, String>>() { + }.getType(); + Map<String, String> gsonMap = new Gson().fromJson(passedParametersJsonStr, type); + passMap.putAll(gsonMap); + } catch (JsonParseException e) { + returnList.add("WARNING: Could not parse passed parameters"); + } } } - if (!(configuredResource==null || configuredResource.equalsIgnoreCase(CACHE_CONSTANT))) { + if (!(configuredResource == null || configuredResource.equalsIgnoreCase(CACHE_CONSTANT))) { eventText = getEventTextFromConfiguredResourceConfig(proj, subj, exp, configuredResource); passMap.put("configuredResource", configuredResource); - + + } + if (eventText!=null) { + buildWorkflow(proj,subj,exp,passMap,eventText); } - if (buildPathParam!=null) { + 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>"); + automationEvent.setParameterMap(passMap); + // Create automationCompletionEvent and launch automation + final String automationId = String.valueOf(System.currentTimeMillis()).concat("-") + .concat(String.valueOf(this.hashCode())); + final AutomationCompletionEvent automationCompletionEvent = new AutomationCompletionEvent(automationId); + final Object sendemailParam = params.get("sendemail"); + if (sendemailParam != null && sendemailParam.toString().equalsIgnoreCase("true")) { + automationCompletionEvent.addNotificationEmailAddr(user.getEmail()); + } + automationEvent.setAutomationCompletionEvent(automationCompletionEvent); + XftEventService eventService = XftEventService.getService(); + if (eventService == null) { + returnList.add("ERROR: Could retrieve event service"); + return; + } + eventService.triggerEvent(automationEvent); + final AutomationCompletionEventListener completionService = AutomationCompletionEventListener.getService(); + List<ScriptOutput> scriptOutputs = null; + for (int i = 1; i < TIMEOUT_SECONDS; i++) { + try { + Thread.sleep(1000); + final AutomationCompletionEvent ace = completionService.getEvent(automationId); + if (ace != null) { + scriptOutputs = ace.getScriptOutputs(); + break; + } else if (i == TIMEOUT_SECONDS) { + returnList.add("<br><b>TIMEOUT WAITING FOR SCRIPT TO RETURN.<b></br>"); + } + } catch (InterruptedException e) { + // Do nothing for now. + } + } + if (scriptOutputs != null && scriptOutputs.size() > 0) { + 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(); + } catch (IOException e) { returnList.add(scriptOut.getResults().toString().replace("\n", "<br>")); } - if (scriptOut.getOutput()!=null && scriptOut.getOutput().length()>0) { - returnList.add("<br><b>SCRIPT STDOUT</b>"); + } + 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(); + } catch (IOException e) { returnList.add(scriptOut.getOutput().replace("\n", "<br>")); } - if (scriptOut.getErrorOutput()!=null && scriptOut.getErrorOutput().length()>0) { - returnList.add("<br><b>SCRIPT STDERR/EXCEPTION</b>"); + } + 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(); + } catch (IOException e) { returnList.add(scriptOut.getErrorOutput().replace("\n", "<br>")); } } } + returnList.add("<br><b>FINISHED PROCESSING"); } - - returnList.add("<br><b>FINISHED PROCESSING"); - } + - private PersistentWorkflowI buildWorkflow(XnatProjectdata proj,Map<String, Object> passMap,String eventText) { + private PersistentWorkflowI buildWorkflow(XnatProjectdata proj,XnatSubjectdata subj,XnatExperimentdata exp, 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(), + wrk = PersistentWorkflowUtils.buildOpenWorkflow(user, params.get("xsiType").toString(), + (exp.getId()!=null) ? exp.getId() : (subj.getId()!=null) ? subj.getId() : 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()); @@ -374,122 +729,54 @@ public class AutomationBasedImporter extends ImporterHandlerA implements Callabl 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) { + + /** + * Gets the event text from configured resource config. + * + * @param proj + * the proj + * @param subj + * the subj + * @param exp + * the exp + * @param configuredResource + * the configured resource + * @return the event text from configured resource config + */ + // 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) { + // Do we need to handle scope differently? I don't think site configured uploads will pass through this method. + // It's really only for configured resources, which should always be scoped at the project level, I think. + final Scope scope = (exp != null) ? Scope.Project : (subj != null) ? Scope.Project : (proj != null ) ? Scope.Project : Scope.Site; + 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++) { + 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); + logger.warn("WARNING: Could not parse resource_config json results (PROJECT=" + proj.getId() + + ",CONFIG_TOOL=" + CONFIG_TOOL + ",CONFIG_PATH=" + CONFIG_SCRIPT_PATH); } } return null; } + /** + * Checks if is zip file name. + * + * @param fileName + * the file name + * @return true, if is zip file name + */ private boolean isZipFileName(final String fileName) { for (final String ext : ZIP_EXT) { if (fileName.toLowerCase().endsWith(ext)) { @@ -499,11 +786,18 @@ public class AutomationBasedImporter extends ImporterHandlerA implements Callabl return false; } + /** + * Gets the zipper. + * + * @param fileName + * the file name + * @return the zipper + */ private ZipI getZipper(final String fileName) { - + // Assume file name represents correct compression method String file_extension = null; - if (fileName!=null && fileName.indexOf(".")!=-1) { + if (fileName != null && fileName.indexOf(".") != -1) { file_extension = fileName.substring(fileName.lastIndexOf(".")).toLowerCase(); if (Arrays.asList(ZIP_EXT).contains(file_extension)) { return new ZipUtils(); @@ -517,8 +811,7 @@ public class AutomationBasedImporter extends ImporterHandlerA implements Callabl } // Assume zip-compression for unnamed inbody files return new ZipUtils(); - + } } - diff --git a/src/main/java/org/nrg/xnat/restlet/resources/EventResource.java b/src/main/java/org/nrg/xnat/restlet/resources/EventResource.java deleted file mode 100644 index 3c1fa83987e032ca02f7b93c0efe706ee39c3c48..0000000000000000000000000000000000000000 --- a/src/main/java/org/nrg/xnat/restlet/resources/EventResource.java +++ /dev/null @@ -1,296 +0,0 @@ -package org.nrg.xnat.restlet.resources; - -import org.apache.commons.lang3.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 { - - private static final String PROPERTY_EVENT_ID = "event_id"; - private static final String PROPERTY_EVENT_LABEL = "event_label"; - - public EventResource(Context context, Request request, Response response) throws ResourceException { - super(context, request, response); - - 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(getSerializer().toJson(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(getSerializer().deserializeJson(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/resources/ScriptTriggerResource.java b/src/main/java/org/nrg/xnat/restlet/resources/ScriptTriggerResource.java index efb570006e92de8cb8b34b3377472683f9ab721c..0b8330d3595a63e3263031b99cb4169f928293e4 100644 --- a/src/main/java/org/nrg/xnat/restlet/resources/ScriptTriggerResource.java +++ b/src/main/java/org/nrg/xnat/restlet/resources/ScriptTriggerResource.java @@ -3,13 +3,14 @@ package org.nrg.xnat.restlet.resources; import org.apache.commons.lang3.StringUtils; import org.nrg.action.ClientException; import org.nrg.action.ServerException; +import org.nrg.automation.entities.EventFilters; import org.nrg.automation.entities.ScriptTrigger; -import org.nrg.automation.services.EventService; import org.nrg.automation.services.ScriptTriggerService; import org.nrg.framework.constants.Scope; import org.nrg.xdat.XDAT; import org.nrg.xdat.security.helpers.Roles; import org.nrg.xft.XFTTable; +import org.python.google.common.collect.Sets; import org.restlet.Context; import org.restlet.data.*; import org.restlet.resource.Representation; @@ -19,6 +20,9 @@ import org.restlet.resource.Variant; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + import java.io.IOException; import java.util.*; @@ -32,22 +36,23 @@ public class ScriptTriggerResource extends AutomationResource { getVariants().add(new Variant(MediaType.TEXT_XML)); getVariants().add(new Variant(MediaType.TEXT_PLAIN)); - _eventService = XDAT.getContextService().getBean(EventService.class); _scriptTriggerService = XDAT.getContextService().getBean(ScriptTriggerService.class); _eventId = (String) getRequest().getAttributes().get(EVENT_ID); final String triggerId = (String) getRequest().getAttributes().get(TRIGGER_ID); + final String Id = (String) getRequest().getAttributes().get(ID); final boolean hasEvent = StringUtils.isNotBlank(_eventId); final boolean hasTriggerId = StringUtils.isNotBlank(triggerId); + final boolean hasId = StringUtils.isNotBlank(Id); final String projectId; - if (!hasTriggerId && !hasEvent) { + if (!hasTriggerId && !hasEvent && !hasId) { projectId = getProjectId(); _trigger = null; } else { - if (hasTriggerId) { - _trigger = _scriptTriggerService.getByTriggerId(triggerId); + if (hasId || hasTriggerId) { + _trigger = (hasId) ? _scriptTriggerService.getById(Id) : _scriptTriggerService.getByTriggerId(triggerId); if (_trigger == null) { throw new ResourceException(Status.CLIENT_ERROR_NOT_FOUND, "Can't find script trigger with ID: " + triggerId); } @@ -59,10 +64,14 @@ public class ScriptTriggerResource extends AutomationResource { } } else if (hasProjectId()) { projectId = getProjectId(); - _trigger = _scriptTriggerService.getByAssociationAndEvent(Scope.encode(Scope.Project, projectId), _eventId); + _trigger = null; + // TODO: For these to make sense now, we need to pass at minimum an event class, but really we would need filters + //_trigger = _scriptTriggerService.getByAssociationAndEvent(Scope.encode(Scope.Project, projectId), _eventId); } else { projectId = null; - _trigger = _scriptTriggerService.getByAssociationAndEvent(Scope.Site.code(), _eventId); + _trigger = null; + // TODO: For these to make sense now, we need to pass at minimum an event class, but really we would need filters + //_trigger = _scriptTriggerService.getByAssociationAndEvent(Scope.Site.code(), _eventId); } } @@ -101,7 +110,7 @@ public class ScriptTriggerResource extends AutomationResource { @Override protected String getResourceId() { - return _trigger == null ? null : _trigger.getEvent().getEventId(); + return _trigger == null ? null : _trigger.getEvent(); } @Override @@ -158,10 +167,11 @@ public class ScriptTriggerResource extends AutomationResource { final Map<String, String> association = Scope.decode(trigger.getAssociation()); final Map<String, String> properties = new HashMap<>(); + properties.put("id", String.valueOf(trigger.getId())); properties.put("triggerId", trigger.getTriggerId()); properties.put("scope", association.get("scope")); properties.put("entityId", association.get("entityId")); - properties.put("event", trigger.getEvent().getEventId()); + properties.put("event", trigger.getEvent()); properties.put("scriptId", trigger.getScriptId()); properties.put("description", trigger.getDescription()); @@ -192,10 +202,13 @@ public class ScriptTriggerResource extends AutomationResource { } ArrayList<String> columns = new ArrayList<>(); + columns.add("id"); columns.add("triggerId"); columns.add("scope"); columns.add("entityId"); + columns.add("srcEventClass"); columns.add("event"); + columns.add("eventFilters"); columns.add("scriptId"); columns.add("description"); @@ -215,10 +228,14 @@ public class ScriptTriggerResource extends AutomationResource { final Map<String, String> atoms = Scope.decode(trigger.getAssociation()); final String scope = atoms.get("scope"); final String entityId = scope.equals(Scope.Site.code()) ? "" : atoms.get("entityId"); - table.insertRowItems(trigger.getTriggerId(), + table.insertRowItems( + String.valueOf(trigger.getId()), + trigger.getTriggerId(), scope, entityId, - trigger.getEvent().getEventId(), + trigger.getSrcEventClass(), + trigger.getEvent(), + trigger.getEventFiltersAsMap(), trigger.getScriptId(), trigger.getDescription()); } @@ -238,62 +255,90 @@ public class ScriptTriggerResource extends AutomationResource { } MediaType mediaType = entity.getMediaType(); - if (!mediaType.equals(MediaType.APPLICATION_WWW_FORM) && !mediaType.equals(MediaType.APPLICATION_JSON)) { - throw new ClientException(Status.CLIENT_ERROR_UNSUPPORTED_MEDIA_TYPE, "This function currently only supports " + MediaType.APPLICATION_WWW_FORM + " and " + MediaType.APPLICATION_JSON); + if (!mediaType.equals(MediaType.APPLICATION_JSON)) { + throw new ClientException(Status.CLIENT_ERROR_UNSUPPORTED_MEDIA_TYPE, "This function currently only supports " + MediaType.APPLICATION_JSON); + } + + //final Properties properties; + JsonResults jsonResults; + try { + final String text = entity.getText(); + final String jsonString = text; + final GsonBuilder builder = new GsonBuilder(); + final Gson gson = builder.create(); + jsonResults = gson.fromJson(jsonString,JsonResults.class); + } catch (IOException e) { + throw new ServerException(Status.SERVER_ERROR_INTERNAL, "An error occurred processing the script properties", e); } - - final Properties properties = decodeProperties(entity, mediaType); // TODO: These remove definitions of scope, entity ID, and script ID that may be passed in on the API call. // TODO: We may consider throwing an exception if something in the body parameters contradicts the URI // TODO: parameters. For example, if the URL indicates site scope, but the body parameters specify project and // TODO: ID, it may be worth throwing an exception and indicating that you should only specify that stuff in the // TODO: URL. For now, though, we'll just ignore the payload parameters for simplicity. + /* + final String scope; + final String entityId; if (getScope() == Scope.Project) { - properties.setProperty("scope", Scope.Project.code()); - properties.setProperty("entityId", getProjectId()); + scope = Scope.Project.code(); + entityId = getProjectId(); } else { - properties.setProperty("scope", Scope.Site.code()); - properties.remove("entityId"); + scope = Scope.Site.code(); + entityId = null; } + */ if (_trigger == null) { - if (!properties.containsKey("event")) { + if (jsonResults.getEvent() == null || jsonResults.getEvent().length()<1) { throw new ClientException(Status.CLIENT_ERROR_BAD_REQUEST, "You must specify the event for your new script trigger."); } - if (!properties.containsKey("scriptId")) { + if (jsonResults.getScriptId() == null || jsonResults.getScriptId().length()<1) { throw new ClientException(Status.CLIENT_ERROR_BAD_REQUEST, "You must specify the script ID for your new script trigger."); } if (_log.isDebugEnabled()) { _log.debug("Creating new script trigger"); } - final String scriptId = properties.getProperty("scriptId"); - final String event = properties.getProperty("event"); - final String triggerId = properties.getProperty("triggerId", _scriptTriggerService.getDefaultTriggerName(scriptId, getScope(), getProjectId(), event)); - final String description = properties.getProperty("description", null); - final ScriptTrigger trigger = _scriptTriggerService.newEntity(triggerId, description, scriptId, getAssociation(), event); + final String scriptId = jsonResults.getScriptId(); + final String event = jsonResults.getEvent(); + final String description = jsonResults.getDescription(); + final String eventClass = jsonResults.getEventClass(); + final Map<String,List<String>> eventFilters = jsonResults.getFilters(); + final String triggerId = _scriptTriggerService.getDefaultTriggerName(scriptId, getScope(), getProjectId(), eventClass, event, eventFilters); + final ScriptTrigger trigger = _scriptTriggerService.newEntity(triggerId, description, scriptId, getAssociation(), eventClass, event, eventFilters); if (_log.isInfoEnabled()) { _log.info("Created a new trigger: " + trigger.toString()); } recordAutomationEvent(triggerId, getAssociation(), "Create", ScriptTrigger.class); + // Return thie trigger ID in the response test. The upload UI needs it + this.getResponse().setEntity(new StringRepresentation(triggerId)); } else { - final String scriptId = properties.getProperty("scriptId"); - final String event = properties.getProperty("event"); - final String triggerId = properties.getProperty("triggerId"); - final String description = properties.getProperty("description", null); + final String scriptId = jsonResults.getScriptId(); + final String event = jsonResults.getEvent(); + final String description = jsonResults.getDescription(); + final String eventClass = jsonResults.getEventClass(); + final Map<String,List<String>> eventFilters = jsonResults.getFilters(); + final String triggerId = _scriptTriggerService.getDefaultTriggerName(scriptId, getScope(), getProjectId(), eventClass, event, eventFilters); boolean isDirty = false; if (StringUtils.isNotBlank(scriptId) && !scriptId.equals(_trigger.getScriptId())) { _trigger.setScriptId(scriptId); isDirty = true; } - if (StringUtils.isNotBlank(event) && !event.equals(_trigger.getEvent().getEventId())) { - _trigger.setEvent(_eventService.getByEventId(event)); + if (StringUtils.isNotBlank(event) && !event.equals(_trigger.getEvent())) { + _trigger.setEvent(event); isDirty = true; } if (StringUtils.isNotBlank(triggerId) && !triggerId.equals(_trigger.getTriggerId())) { _trigger.setTriggerId(triggerId); isDirty = true; } + if (StringUtils.isNotBlank(eventClass) && !eventClass.equals(_trigger.getSrcEventClass())) { + _trigger.setSrcEventClass(eventClass); + isDirty = true; + } + if (eventFilters != null && !eventFilters.equals(_trigger.getEventFiltersAsMap())) { + _trigger.setEventFiltersAsMap(eventFilters); + isDirty = true; + } // Description is a little different because you could specify an empty description. if (description != null && !description.equals(_trigger.getDescription())) { _trigger.setDescription(description); @@ -306,11 +351,23 @@ public class ScriptTriggerResource extends AutomationResource { if (isDirty) { _scriptTriggerService.update(_trigger); recordAutomationEvent(triggerId, getAssociation(), "Update", ScriptTrigger.class); + // Return thie trigger ID in the response test. The upload UI needs it + this.getResponse().setEntity(new StringRepresentation(triggerId)); } } } - private String formatScopeEntityIdAndEvent() { + @SuppressWarnings("unused") + private Set<EventFilters> getEventFilters(Map<String, List<String>> filters) { + final Set<EventFilters> eventSet = Sets.newHashSet(); + for (final String filterKey : filters.keySet()) { + EventFilters ef = new EventFilters(filterKey,filters.get(filterKey)); + eventSet.add(ef); + } + return eventSet; + } + + private String formatScopeEntityIdAndEvent() { final StringBuilder buffer = new StringBuilder(); if (_trigger != null) { final Map<String, String> atoms = Scope.decode(_trigger.getAssociation()); @@ -320,7 +377,7 @@ public class ScriptTriggerResource extends AutomationResource { buffer.append("project ").append(atoms.get("entityId")); } if (_trigger.getEvent() != null) { - buffer.append(" and event ").append(_trigger.getEvent().getEventId()); + buffer.append(" and event ").append(_trigger.getEvent()); } else { buffer.append(", no event"); } @@ -337,14 +394,64 @@ public class ScriptTriggerResource extends AutomationResource { } } return buffer.toString(); + } + + @SuppressWarnings("unused") + private class JsonResults { + private String event; + private String eventClass; + private String scriptId; + private String description; + private Map<String,List<String>> filters; + + public String getEvent() { + return event; + } + + public void setEvent(String event) { + this.event = event; + } + + public String getEventClass() { + return eventClass; + } + + public void setEventClass(String eventClass) { + this.eventClass = eventClass; + } + + public String getScriptId() { + return scriptId; + } + + public void setScriptId(String scriptId) { + this.scriptId = scriptId; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Map<String, List<String>> getFilters() { + return filters; + } + + public void setFilters(Map<String, List<String>> filters) { + this.filters = filters; + } + } private static final Logger _log = LoggerFactory.getLogger(ScriptTriggerResource.class); private static final String EVENT_ID = "EVENT_ID"; private static final String TRIGGER_ID = "TRIGGER_ID"; + private static final String ID = "ID"; - private final EventService _eventService; private final ScriptTriggerService _scriptTriggerService; private final String _eventId; diff --git a/src/main/java/org/nrg/xnat/services/messaging/automation/AutomatedScriptRequest.java b/src/main/java/org/nrg/xnat/services/messaging/automation/AutomatedScriptRequest.java index 14b13b054b5454b18249206c25826add10c05056..3937cfadfe8d3af9a7b2e68fcc1b8c2f8371ee41 100644 --- a/src/main/java/org/nrg/xnat/services/messaging/automation/AutomatedScriptRequest.java +++ b/src/main/java/org/nrg/xnat/services/messaging/automation/AutomatedScriptRequest.java @@ -1,13 +1,19 @@ package org.nrg.xnat.services.messaging.automation; +import org.json.JSONObject; import org.nrg.xft.security.UserI; +import com.google.common.collect.Maps; + import java.io.Serializable; +import java.util.Map; public class AutomatedScriptRequest implements Serializable { - private static final long serialVersionUID = 6828367474920729056L; - private final String _srcWorkflowId; + private static final long serialVersionUID = -5425712284737600869L; + + private final String _srcEventId; + private final String _srcEventClass; private final UserI _user; private final String _scriptId; private final String _event; @@ -15,9 +21,11 @@ public class AutomatedScriptRequest implements Serializable { private final String _externalId; private final String _dataType; private final String _dataId; + private final Map<String,Object> _argumentMap = Maps.newHashMap(); - public AutomatedScriptRequest(final String srcWorkflowId, final UserI user, final String scriptId, final String event, final String scriptWorkflow, final String dataType, final String dataId, final String externalId) { - _srcWorkflowId = srcWorkflowId; + public AutomatedScriptRequest(final String srcEventId, final String srcEventClass, final UserI user, final String scriptId, final String event, final String scriptWorkflow, final String dataType, final String dataId, final String externalId) { + _srcEventId = srcEventId; + _srcEventClass = srcEventClass; _user = user; _scriptId = scriptId; _event = event; @@ -26,9 +34,18 @@ public class AutomatedScriptRequest implements Serializable { _dataId = dataId; _externalId = externalId; } + + public AutomatedScriptRequest(final String srcEventId, final String srcEventClass, final UserI user, final String scriptId, final String event, final String scriptWorkflow, final String dataType, final String dataId, final String externalId, Map<String,Object> argumentMap) { + this(srcEventId, srcEventClass, user, scriptId, event, scriptWorkflow, dataType, dataId, externalId); + _argumentMap.putAll(argumentMap); + } - public String getSrcWorkflowId() { - return _srcWorkflowId; + public String getSrcEventId() { + return _srcEventId; + } + + public String getSrcEventClass() { + return _srcEventClass; } public UserI getUser() { @@ -58,4 +75,12 @@ public class AutomatedScriptRequest implements Serializable { public String getDataId() { return _dataId; } + + public Map<String,Object> getArgumentMap() { + return _argumentMap; + } + + public String getArgumentJson() { + return new JSONObject(_argumentMap).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 7b87c90eeaa20e506b116a3e616597185878b77a..6b93b22ffba2151b0fba3e3464dff36fd30952c5 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,6 +1,5 @@ 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; @@ -16,7 +15,6 @@ import java.util.Map; public class AutomatedScriptRequestListener { - @SuppressWarnings("unused") public void onRequest(final AutomatedScriptRequest request) throws Exception { final PersistentWorkflowI workflow = WorkflowUtils.getUniqueWorkflow(request.getUser(), request.getScriptWorkflowId()); workflow.setStatus(PersistentWorkflowUtils.IN_PROGRESS); @@ -26,16 +24,21 @@ public class AutomatedScriptRequestListener { parameters.put("user", request.getUser()); parameters.put("scriptId", request.getScriptId()); parameters.put("event", request.getEvent()); - parameters.put("srcWorkflowId", request.getSrcWorkflowId()); + parameters.put("srcEventId", request.getSrcEventId()); + final String srcEventClass = request.getSrcEventClass(); + parameters.put("srcEventClass", srcEventClass); + // For backwards compatibility + if (srcEventClass.contains("WorkflowStatusEvent") || srcEventClass.contains("WrkWorkflowdata")) { + parameters.put("srcWorkflowId", request.getArgumentMap().get("wrkWorkflowId")); + } parameters.put("scriptWorkflowId", request.getScriptWorkflowId()); parameters.put("dataType", request.getDataType()); parameters.put("dataId", request.getDataId()); parameters.put("externalId", request.getExternalId()); parameters.put("workflow", workflow); - + parameters.put("arguments", request.getArgumentJson()); try { _service.runScript(_service.getScript(request.getScriptId()), parameters); - if (PersistentWorkflowUtils.IN_PROGRESS.equals(workflow.getStatus())) { WorkflowUtils.complete(workflow, workflow.buildEvent()); } diff --git a/src/main/webapp/scripts/project/projResourceMgmt.js b/src/main/webapp/scripts/project/projResourceMgmt.js index 6eca65edc30d360c9f42413a3621180dc749c978..43ce30ea34766f46392c4986c686c9e16e28294e 100644 --- a/src/main/webapp/scripts/project/projResourceMgmt.js +++ b/src/main/webapp/scripts/project/projResourceMgmt.js @@ -155,6 +155,8 @@ XNAT.app.pResources={ if (scriptToRun == XNAT.app.pResources.scripts[i]["Script ID"]) { var eventData = { event: ("Uploaded " + props.name), scriptId: scriptToRun, + eventClass: "org.nrg.xnat.event.entities.WorkflowStatusEvent", + filters: { "status":["Complete"] }, description: "Run " + scriptToRun + " upon " + props.name + " upload." }; var eventHandlerAjax = $.ajax({ type : "PUT", @@ -167,6 +169,7 @@ XNAT.app.pResources={ }); eventHandlerAjax.done( function( data, textStatus, jqXHR ) { console.log("NOTE: Event handler added for " + props.name + " upload"); + props.triggerId = jqXHR.responseText; // Configure uploader var getUploadConfigAjax = $.ajax({ type : "GET", @@ -195,10 +198,12 @@ XNAT.app.pResources={ } var newHandlerObj = { event:NEW_HANDLER, + eventTriggerId:props.triggerId, eventScope:"prj", launchFromResourceUploads:true, launchFromCacheUploads:false, launchWithoutUploads:false, + doNotUseUploader:false, contexts:[props.type], resourceConfigs:[props.name] }; diff --git a/src/main/webapp/scripts/uploaders/AutomationBasedUploader.js b/src/main/webapp/scripts/uploaders/AutomationBasedUploader.js index c52b43363fe5a0049a7de637cd894be19576ad7e..93c8bcd687d5ece04869ba344726f6fdaa4d9ce3 100644 --- a/src/main/webapp/scripts/uploaders/AutomationBasedUploader.js +++ b/src/main/webapp/scripts/uploaders/AutomationBasedUploader.js @@ -1,5 +1,8 @@ -//Copyright 2015 Washington University -//Author: Mike Hodge <hodgem@mir.wustl.edu> +/** + * Copyright 2015 Washington University + * Automation Based Uploader + * Author: Mike Hodge <hodgem@mir.wustl.edu> + */ /* * resource dialog is used to upload resources at any level @@ -270,6 +273,7 @@ XNAT.app.abu.eventHandlerChange = function(){ } else if ((XNAT.app.abu.usageSelect=='Launch') || (abu._fileUploader._uploadStarted && abu._fileUploader._filesInProgress<1)) { $("#abu-process-button").removeClass("abu-button-disabled"); } + XNAT.app.abu.filesProcessed = false; } XNAT.app.abu.initUploaderConfig = function(){ @@ -285,6 +289,13 @@ XNAT.app.abu.initUploaderConfig = function(){ uploaderConfigAjax.done( function( data, textStatus, jqXHR ) { if (typeof data !== 'undefined' && $.isArray(data)) { + // Configurations must have trigger IDs (remove old-style (pre XNAT 1.7) configurations) + for (var i = data.length -1; i >= 0 ; i--) { + var triggerId = data[i].eventTriggerId; + if (typeof triggerId == 'undefined' || triggerId.length<1) { + data.splice(i,1); + } + } XNAT.app.abu.uploaderConfig = data; } else { XNAT.app.abu.uploaderConfig = []; @@ -299,6 +310,13 @@ XNAT.app.abu.initUploaderConfig = function(){ }); uploaderSiteConfigAjax.done( function( data, textStatus, jqXHR ) { if (typeof data !== 'undefined' && $.isArray(data) && data.length>0) { + // Configurations must have trigger IDs (remove old-style (pre XNAT 1.7) configurations) + for (var i = data.length -1; i >= 0 ; i--) { + var triggerId = data[i].eventTriggerId; + if (typeof triggerId == 'undefined' || triggerId.length<1) { + data.splice(i,1); + } + } Array.prototype.push.apply(XNAT.app.abu.uploaderConfig,data); } }); @@ -355,7 +373,7 @@ XNAT.app.abu.populateEventHandlerSelect = function(){ 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 (currEvent.triggerId==currConfig.eventTriggerId && currEvent.scope==currConfig.eventScope) { var doAssign = true; if (usageSelect == 'Upload' && resourceSelect==XNAT.app.abu.cacheConstant && !currConfig.launchFromCacheUploads) { doAssign = false; @@ -364,7 +382,8 @@ XNAT.app.abu.populateEventHandlerSelect = function(){ } 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) { + } else if ((typeof currConfig.resourceConfigs === 'undefined') || + (currConfig.resourceConfigs.length>0 && $.inArray(resourceSelect,currConfig.resourceConfigs)<0)) { doAssign = false; } } @@ -372,14 +391,15 @@ XNAT.app.abu.populateEventHandlerSelect = function(){ doAssign = false; } if (doAssign) { - $('#eventHandlerSelect').append('<option value="' + currEvent.event + '" class="' + currEvent.scope + '">' + + $('#eventHandlerSelect').append('<option value="' + currEvent.triggerId + '" 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>'); + // We now don't want to include event handlers with no upload configuration in the display. The default for handlers is to not use the uploader. + //$('#eventHandlerSelect').append('<option value="' + currEvent.triggerId + '" 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'); @@ -400,6 +420,8 @@ XNAT.app.abu.populateWhatToDoSelect = function(){ var uploaderConfig = XNAT.app.abu.uploaderConfig; var usageSelect = $('#usageSelect').val(); var resourceSelect = $('#resourceSelect').val(); + $('#whatToDoSelect').append('<option id="handlerDefaultOption" value="">' + + ((usageSelect=='Launch' || resourceSelect==XNAT.app.abu.cacheConstant) ? 'SELECT' : 'DEFAULT') + '</option>'); if (XNAT.app.abu.contextResourceConfigs!=undefined && XNAT.app.abu.contextResourceConfigs.length>0) { for (var h=0; h<resourceConfigs.length; h++) { var resourceMatch = false; @@ -410,7 +432,7 @@ XNAT.app.abu.populateWhatToDoSelect = function(){ 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) { + if (currEvent.triggerId==currConfig.eventTriggerId && currEvent.scope==currConfig.eventScope) { var doAssign = true; if ((usageSelect == 'Launch') || (!(currConfig.launchFromResourceUploads)) || @@ -418,7 +440,7 @@ XNAT.app.abu.populateWhatToDoSelect = function(){ doAssign = false; } if (doAssign) { - $('#whatToDoSelect').append('<option value="resource- ' + resourceConfigs[h].name + ':launch-' + currEvent.event + '" class="' + currEvent.scope + '">' + + $('#whatToDoSelect').append('<option value="resource- ' + resourceConfigs[h].name + ':launch-' + currEvent.triggerId + '" 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; @@ -426,10 +448,11 @@ XNAT.app.abu.populateWhatToDoSelect = function(){ 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; + // We now don't want to include event handlers with no upload configuration in the display. The default for handlers is to not use the uploader. + //$('#whatToDoSelect').append('<option value="resource-' + resourceConfigs[h].name + ':launch-' + currEvent.triggerId + '" 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; } } } @@ -444,7 +467,7 @@ XNAT.app.abu.populateWhatToDoSelect = function(){ 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 (currEvent.triggerId==currConfig.eventTriggerId && currEvent.scope==currConfig.eventScope) { var doAssign = true; if ((usageSelect == 'Launch') || (!(currConfig.launchFromCacheUploads)) || @@ -452,16 +475,22 @@ XNAT.app.abu.populateWhatToDoSelect = function(){ doAssign = false; } if (doAssign) { - $('#whatToDoSelect').append('<option value="resource-' + XNAT.app.abu.cacheConstant + ':launch-' + currEvent.event + '" class="' + currEvent.scope + '">' + "Upload --> " + + $('#whatToDoSelect').append('<option value="resource-' + XNAT.app.abu.cacheConstant + ':launch-' + currEvent.triggerId + '" 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>'); + // We now don't want to include event handlers with no upload configuration in the display. The default for handlers is to not use the uploader. + //$('#whatToDoSelect').append('<option value="resource-' + XNAT.app.abu.cacheConstant + ':launch-' + currEvent.triggerId + '" class="' + currEvent.scope + '">' + "Upload --> " + + // ((typeof(currEvent.description) !== 'undefined' && currEvent.description.length>0) ? currEvent.description : currEvent.scriptId) + '</option>'); } } + if ($('#whatToDoSelect').find('option').length==1) { + $('#whatToDoOption').html('NONE DEFINED'); + } else if ($('#whatToDoSelect').find('option').length==2) { + $('#whatToDoSelect').find('option').get(0).remove(); + } XNAT.app.abu.whatToDoChange(); } @@ -524,9 +553,17 @@ XNAT.app.abu.initializeAbuUploader = function(usageType){ 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(); + if (abu._fileUploader.uploadsStarted>0 && abu._fileUploader.uploadsInProgress==0) { + XNAT.app.abu.updateResourceStats(); + // Create workflow if we just uploaded files without any script processing (otherwise, workflow will have been generated there) + if (!XNAT.app.abu.filesProcessed) { + XNAT.app.abu.sendWorkflowWhenDone(); + } + } xModalCloseNew(XNAT.app.abu.abuConfigs.modalOpts.id); + //if (abu._fileUploader.uploadsStarted>0 && abu._fileUploader.uploadsInProgress==0) { + // window.location.reload(true); + //} }, uploadStartedFunction:function(){ if (abu._fileUploader._currentAction.indexOf("import-handler=" + XNAT.app.abu.importHandler)>=0 && (typeof(XNAT.app.abu.buildPath) == 'undefined' || XNAT.app.abu.buildPath == '')) { @@ -538,7 +575,9 @@ XNAT.app.abu.initializeAbuUploader = function(usageType){ $("#abu-done-button").addClass("abu-button-disabled"); } $("#resourceSelect").prop('disabled','disabled'); - $("#whatToDoSelect").prop('disabled','disabled'); + if ($("#whatToDoSelect").val() != "") { + $("#whatToDoSelect").prop('disabled','disabled'); + } $("#usageSelect").prop('disabled','disabled'); }, uploadCompletedFunction:function(){ @@ -554,6 +593,7 @@ XNAT.app.abu.initializeAbuUploader = function(usageType){ XNAT.app.abu.processFiles(); }, showEmailOption:true, + showCloseOption:true, showExtractOption:(usageType !== 'launch'), showVerboseOption:false, showUpdateOption:false, @@ -575,11 +615,20 @@ XNAT.app.abu.initializeAbuUploader = function(usageType){ $(".upload-area").css("display","none"); $(".whattodo-area").css("display","none"); $("#abu-upload-button").addClass("abu-button-disabled"); + abu._fileUploader.DRAG_AND_DROP_ON = false; $("#abu-upload-button").css("display","none"); $("#abu-process-button-text").html("Run script"); + $("#abu-done-button-text").html("Cancel"); + if ($('#eventHandlerSelect option').size()>1 && $('#eventHandlerSelect').val()=="") { + $("#abu-process-button").addClass("abu-button-disabled"); + } $("#file-uploader-instructions-sel").css("display","none"); } else { XNAT.app.abu.populateWhatToDoSelect(); + if ($('#whatToDoSelect option').size()>1 && $('#whatToDoSelect').val()=="") { + $("#abu-upload-button").addClass("abu-button-disabled"); + abu._fileUploader.DRAG_AND_DROP_ON = false; + } $("#abu-process-button").addClass("abu-button-disabled"); $(".upload-area").css("display","none"); $(".eventhandler-area").css("display","none"); @@ -604,6 +653,7 @@ XNAT.app.abu.usageSelectAction = function(){ XNAT.app.abu.populateEventHandlerSelect(); $("#abu-done-button").removeClass("abu-button-disabled"); $("#abu-upload-button").removeClass("abu-button-disabled"); + abu._fileUploader.DRAG_AND_DROP_ON = true; $("#abu-process-button").addClass("abu-button-disabled"); $("#script-select-text").html("Post-upload processing script:"); $("#abu-process-button-text").html("Process files"); @@ -613,6 +663,7 @@ XNAT.app.abu.usageSelectAction = function(){ XNAT.app.abu.populateEventHandlerSelect(); $("#abu-done-button").removeClass("abu-button-disabled"); $("#abu-upload-button").addClass("abu-button-disabled"); + abu._fileUploader.DRAG_AND_DROP_ON = false; var eventHandler = $('#eventHandlerSelect').val(); if (eventHandler != undefined && eventHandler != null && eventHandler.length>0) { $("#abu-process-button").removeClass("abu-button-disabled"); @@ -636,7 +687,9 @@ XNAT.app.abu.updateModalAction = function(){ 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; + var subdir = resourceConfigs[i].subdir; + subdir = (typeof subdir !== 'undefined' && subdir.length > 0) ? "/" + subdir : subdir; + abu._fileUploader._currentAction = $(XNAT.app.abu.currentLink).attr("data-uri") + "/resources/" + resourceConfigs[i].label + "/files" + subdir + "/##FILENAME_REPLACE##?overwrite=" + resourceConfigs[i].overwrite + "&update-stats=false&XNAT_CSRF=" + window.csrfToken; XNAT.app.abu.populateEventHandlerSelect(); return; } @@ -654,6 +707,17 @@ XNAT.app.abu.whatToDoChange = function(){ $('#resourceSelect').val(resourceSelect); XNAT.app.abu.updateModalAction(); $('#eventHandlerSelect').val(launchSelect); + if (typeof abu !== 'undefined' && abu._fileUploader.uploadsStarted>0 && abu._fileUploader.uploadsInProgress==0) { + $("#abu-process-button").removeClass("abu-button-disabled"); + } + if (XNAT.app.abu.usageSelect == 'Upload' && $('#whatToDoSelect option').size()>1 && $('#whatToDoSelect').val()=="") { + $("#abu-upload-button").addClass("abu-button-disabled"); + abu._fileUploader.DRAG_AND_DROP_ON = false; + } else if (typeof abu == 'undefined' || abu._fileUploader.uploadsStarted==0) { + $("#abu-upload-button").removeClass("abu-button-disabled"); + abu._fileUploader.DRAG_AND_DROP_ON = true; + } + XNAT.app.abu.filesProcessed = false; } XNAT.app.abu.initializeBuildPath = function(){ @@ -710,8 +774,8 @@ 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"); + abu._fileUploader._currentAction.replace(/\/files[\/?].*$/i,'').replace(/^\/data\//i,"/archive/").replace(/^\/REST\//i,"/archive/" + + "&options=populateStats") + "&XNAT_CSRF=" + window.csrfToken; var updateStatsAjax = $.ajax({ type : "POST", url:serverRoot+updateStatsUrl, @@ -733,14 +797,51 @@ XNAT.app.abu.updateResourceStats=function() { 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 + var params = {}; + params['project'] = XNAT.data.context.projectID; + params['process'] = 'true'; + if (typeof(XNAT.app.abu.configuredResource)!=='undefined' && XNAT.app.abu.configuredResource!=null) { + params['configuredResource'] = XNAT.app.abu.configuredResource; + } + 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; + } + 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, + 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 ) { + console.log(XNAT.app.abu.processReturnedText(data,true)); + }); + processAjax.fail( function( data, textStatus, jqXHR ) { + console.log(XNAT.app.abu.processReturnedText(data,true)); + }); } } } XNAT.app.abu.processFiles=function() { - $(".abu-files-processing").css("display","inline"); + XNAT.app.abu.filesProcessed = true; + + $(".abu-files-processing").css("display","block"); // Since we're using the update-stats=false parameter, we need to call catalog refresh when we're finished uploading. XNAT.app.abu.updateResourceStats(); @@ -751,8 +852,9 @@ XNAT.app.abu.processFiles=function() { var eventHandlerScope = eventHandlerElement.className; } + this.paramsToPass = undefined; for (var i=0;i<this.uploaderConfig.length;i++) { - if (this.uploaderConfig[i].event==eventHandler && this.uploaderConfig[i].eventScope==eventHandlerScope && + 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; break; @@ -837,7 +939,9 @@ XNAT.app.abu.continueProcessing=function() { 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") : "") + + (($("#closeBox").length>0) ? ($("#closeBox").is(':checked')) ? "&sendemail=true" : + (($("#emailBox").length>0) ? (($("#emailBox").is(':checked')) ? "&sendemail=true" : "&sendemail=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") : "") , @@ -894,7 +998,7 @@ XNAT.app.abu.continueProcessing=function() { }); $("#abu-done-button").removeClass("abu-button-disabled"); setTimeout(function(){ - if (document.getElementById("emailBox")!=null && document.getElementById("emailBox").checked) { + if (document.getElementById("closeBox")!=null && document.getElementById("closeBox").checked) { xModalMessage('Notice',"You will be sent an e-mail upon completion"); xModalCloseNew(XNAT.app.abu.abuConfigs.modalOpts.id); } @@ -920,11 +1024,12 @@ XNAT.app.abu.removeUploaderConfiguration=function(configEvent,scope) { } } -XNAT.app.abu.saveUploaderConfiguration=function(configEvent,scope) { - var newConfigObj = { event: configEvent, eventScope: scope }; +XNAT.app.abu.saveUploaderConfiguration=function(configTriggerId, configEvent, scope) { + var newConfigObj = { eventTriggerId: configTriggerId, 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.doNotUseUploader = $('#ULC_RB_doNotUseUploader').is(':checked'); newConfigObj.parameters = undefined; $(".ULC_parametersDiv").each(function() { var parameterField = $(this).find(".ULC_parametersField").val(); @@ -1046,7 +1151,7 @@ XNAT.app.abu.validateUploaderConfiguration=function() { return true; } -XNAT.app.abu.configureUploaderForEventHandler=function(configEvent,scope) { +XNAT.app.abu.configureUploaderForEventHandler=function(configTriggerId, configEvent, scope) { var uploaderConfig = XNAT.app.abu.uploaderConfig; if (typeof(uploaderConfig) === 'undefined' || uploaderConfig == null) { @@ -1056,16 +1161,17 @@ XNAT.app.abu.configureUploaderForEventHandler=function(configEvent,scope) { var configObj; for (var i=0;i<uploaderConfig.length;i++) { var objI = uploaderConfig[i]; - if (objI.eventScope == scope && objI.event == configEvent) { + if (objI.eventScope == scope && objI.eventTriggerId == configTriggerId) { // 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; + configObj.launchFromCacheUploads = false; + configObj.launchFromResourceUploads = false; + configObj.launchWithoutUploads = false; + configObj.doNotUseUploader = true; // best to leave these undefined, I think //configObj.parameters = [ ]; //configObj.contexts = [ 'xnat:projectData','xnat:subjectAssessorData','xnat:imageAssessorData','xnat:imageSessionData','xnat:imageScanData','xnat:subjectData' ]; @@ -1083,7 +1189,7 @@ XNAT.app.abu.configureUploaderForEventHandler=function(configEvent,scope) { close: false, action: function( obj ){ if (XNAT.app.abu.validateUploaderConfiguration()) { - XNAT.app.abu.saveUploaderConfiguration(configEvent,scope); + XNAT.app.abu.saveUploaderConfiguration(configTriggerId, configEvent, scope); obj.close(); } } @@ -1107,6 +1213,8 @@ XNAT.app.abu.configureUploaderForEventHandler=function(configEvent,scope) { ((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 style="margin-left:20px;width:100%"><input type="radio" id="ULC_RB_doNotUseUploader" name="ULC_RB" 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>User Supplied Parameters:</b><p><div id="ULC_parameters">'; diff --git a/src/main/webapp/scripts/uploaders/fileuploader.js b/src/main/webapp/scripts/uploaders/fileuploader.js index e2a1868d1f01c2634081535648e6109586bc0f5e..cd32d43bd8ddbaa3916b31e0b0278b25c859232f 100644 --- a/src/main/webapp/scripts/uploaders/fileuploader.js +++ b/src/main/webapp/scripts/uploaders/fileuploader.js @@ -1,7 +1,9 @@ -// -// Helper functions -// +/** + * Copyright 2015 Washington University + * File uploader + * Author: Mike Hodge (hodgem@wustl.edu) + */ var abu = abu || {}; @@ -16,6 +18,7 @@ abu.FileUploader = function(o){ // 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.DRAG_AND_DROP_ON = true; this.uploadsInProgress = 0; this.uploadsStarted = 0; $(this._options.element).html(""); @@ -23,7 +26,7 @@ abu.FileUploader = function(o){ this.buildUploaderDiv = function() { $(this._options.element).append( '<div class="abu-uploader">' + - '<div id="abu-files-processing" class="abu-files-processing"> Processing uploaded files..... </div>' + + '<div id="abu-files-processing" class="abu-files-processing"> Processing...... </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">' + @@ -46,12 +49,18 @@ abu.FileUploader = function(o){ '</div>' : '<div class="abu-extract-zip"><input id="extractRequestBox" type="hidden" value="1"/></div>' ) + - ((this._options.showEmailOption) ? + ((this._options.showCloseOption) ? '<div class="abu-options-cb" title = "Close window upon submit and send e-mail upon completion">' + - '<input id="emailBox" type="checkbox" value="1">' + + '<input id="closeBox" type="checkbox" value="1">' + 'Close window upon submit' + '</div>' : "" ) + + ((this._options.showEmailOption) ? + '<div class="abu-options-cb" title = "Send e-mail upon completion">' + + '<input id="emailBox" type="checkbox" value="1">' + + 'Send e-mail upon completion' + + '</div>' : "" + ) + ((this._options.showUpdateOption) ? '<div class="abu-options-cb" title = "Update existing records?">' + '<input id="updateBox" type="checkbox" value="1">' + @@ -77,13 +86,26 @@ abu.FileUploader = function(o){ $("#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"); }); + $('#closeBox').change(function(){ + if ($('#closeBox').is(':checked')) { + $('#emailBox').prop('checked', true); + $('#emailBox').attr('disabled', true); + } else { + $('#emailBox').attr('disabled', false); + } + }); + if (this.ALLOW_DRAG_AND_DROP) { $(".abu-upload-drop-area").on('dragover',function(e) { - this.activateUploadArea(e); + if (this.DRAG_AND_DROP_ON) { + this.activateUploadArea(e); + } }.bind(this) ); $(".abu-upload-drop-area").on('dragenter',function(e) { - this.activateUploadArea(e); + if (this.DRAG_AND_DROP_ON) { + this.activateUploadArea(e); + } }.bind(this) ); $(".abu-upload-drop-area").on('drop',function(e) { @@ -99,11 +121,15 @@ abu.FileUploader = function(o){ }.bind(this) ); $(this._options.element).on('dragover',function(e) { - this.activateUploadArea(e); + if (this.DRAG_AND_DROP_ON) { + this.activateUploadArea(e); + } }.bind(this) ).bind(this); $(this._options.element).on('dragenter',function(e) { - this.activateUploadArea(e); + if (this.DRAG_AND_DROP_ON) { + this.activateUploadArea(e); + } }.bind(this) ); } @@ -132,7 +158,7 @@ abu.FileUploader = function(o){ var cFile = fileA[i]; var adj_i = i + start_i; $(".abu-upload-list").append( - '<form id="file-upload-form-' + adj_i + '" action="' + this._currentAction + + '<form id="file-upload-form-' + adj_i + '" action="' + this._currentAction.replace("##FILENAME_REPLACE##",cFile.name) + (($("#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") : "") + @@ -207,10 +233,24 @@ abu.FileUploader = function(o){ 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>'); + // check for duplicates + var isDuplicate = false; + try { + var resultObj = JSON.parse(result); + if (typeof resultObj.duplicates !== 'undefined' && resultObj.duplicates.length==1) { + isDuplicate = true; + } + } catch(e) { + // Do nothing for now + } + if (!isDuplicate) { + 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>'); + } } else { - status.html('<span class="abu-upload-complete abu-upload-complete-text">Upload complete</span>'); + status.html('<a href="javascript:abu._fileUploader.showReturnedText(\'' + $(status).attr('id') + '\')" class="underline abu-upload-fail">Duplicate file and overwrite=false. Not uploaded.</a>'); } status.css("display","inline-block"); $(infoSelector).find(".abu-progress").css("display","none"); diff --git a/src/main/webapp/scripts/xnat/app/automation.js b/src/main/webapp/scripts/xnat/app/automation.js index fef6c53599b8de1df3178ff86af470f59647b619..8e5d42b879f2066827cb4d3f44b92713e66dde73 100644 --- a/src/main/webapp/scripts/xnat/app/automation.js +++ b/src/main/webapp/scripts/xnat/app/automation.js @@ -59,6 +59,7 @@ XNAT.app.automation = {}; //return XNAT.url.restUrl('/scripts/xnat/app/automation-workflows.json'); } + /* function eventsURL(part, params, cacheit){ part = isDefined(part) ? part : ''; // return events URL with CSRF and unique cache-busting paramater @@ -69,6 +70,7 @@ XNAT.app.automation = {}; automation.events = []; return xhr.getJSON(eventsURL(), callback) } + */ function createEventRows(events){ var rows = ''; @@ -111,6 +113,7 @@ XNAT.app.automation = {}; // initialize the Events display + /* automation.initEvents = function(){ // make sure things are hidden first $eventsTable.hide(); @@ -122,6 +125,7 @@ XNAT.app.automation = {}; }); }; + */ function getWorkflows(callback){ //automation.workflows = {}; // reset workflows @@ -208,6 +212,7 @@ XNAT.app.automation = {}; } + /* automation.saveEvent = function(data){ if (!data || !data.event_id){ xmodal.loading.closeAll(); @@ -239,6 +244,7 @@ XNAT.app.automation = {}; } }); }; + */ function toggleDisabled($els, disabled){ @@ -342,6 +348,7 @@ XNAT.app.automation = {}; }) } + /* automation.deleteEvent = function(id, label){ var _url = eventsURL('', '?XNAT_CSRF=' + window.csrfToken + '&cascade=true', false); xmodal.confirm({ @@ -369,10 +376,11 @@ XNAT.app.automation = {}; } }); }; + */ // INITIALIZE - automation.initEvents(); + //automation.initEvents(); // click the "Define Event" button diff --git a/src/main/webapp/scripts/xnat/app/eventsManager.js b/src/main/webapp/scripts/xnat/app/eventsManager.js index 6bb1803c819331f62565cef3000e88125c0f137e..cf236a677d1c9707b9bb179f4dadfb103a6899e6 100644 --- a/src/main/webapp/scripts/xnat/app/eventsManager.js +++ b/src/main/webapp/scripts/xnat/app/eventsManager.js @@ -47,9 +47,22 @@ $(function(){ if(_handlers.length) { handlersRendered = true; + eventRows += + '<dl class="header">' + + '<dl>' + + '<dd class="col1">Event</dd>' + + '<dd class="col2">Script ID</dd>' + + '<dd class="col3">Description</dd>' + + ((doEdit) ? + '<dd class="col4"></dd>' + + '<dd class="col5"></dd>' + : '') + + '</dl>' + + '</dl>'; forEach(_handlers, function(eventHandler){ var _event_id = eventHandler['event']; eventsManager.handlers.push(_event_id); + /* eventRows += '<tr class="highlight">' + '<td class="event-id">' + _event_id + '</td>' + '<td class="script-id">' + eventHandler.scriptId + '</td>' + @@ -65,8 +78,31 @@ $(function(){ '</td>' : '') + '</tr>'; + */ + eventRows += '<dl class="item">' + + '<dd class="col1">' + _event_id + '</dd>' + + '<dd class="col2">' + eventHandler.scriptId + '</dd>' + + '<dd class="col3">' + eventHandler.description + '</dd>' + + ((doEdit) ? + '<dd class="col4" style="text-align: center;">' + + '<button href="javascript:" class="delete-handler event-handler-button" ' + + 'data-handler="' + eventHandler.triggerId + '" ' + + 'data-event="' + _event_id + '" ' + + ' title="Delete handler for event ' + _event_id + '">delete</button>' + + '</dd>' + + '<dd class="col5" style="text-align: center;">' + + '<button href="javascript:" class="configure-uploader-handler event-handler-button" ' + + 'data-handler="' + eventHandler.triggerId + '" ' + + 'data-event="' + _event_id + '" ' + + ' title="Configure uploader for event ' + _event_id + '">configure uploader</button>' + + '</dd>' + : '') + + '<dd class="colC">' + '<b>Event Class: </b> ' + getEventClassDisplayValueFromHandlers(_handlers, eventHandler) + '</dd>' + + '<dd class="colC">' + '<b>Event Filters: </b> ' + eventHandler.eventFilters + '</dd>' + + '</dl>'; }); - $((doEdit) ? events_manage_table : $events_table).find('tbody').html(eventRows); + //$((doEdit) ? events_manage_table : $events_table).find('tbody').html(eventRows); + $((doEdit) ? events_manage_table : $events_table).html(eventRows); $((doEdit) ? events_manage_table : $events_table).show(); } else { @@ -90,7 +126,31 @@ $(function(){ function initEventsMenu(){ eventsManager.events = []; // reset array - return xhr.getJSON({ + + $("#select_event").prop('disabled','disabled'); + if (typeof XNAT.app.eventsManager.eventClasses === 'undefined') { + var eventClassesAjax = $.ajax({ + type : "GET", + url:serverRoot+"/xapi/projects/" + window.projectScope + '/eventHandlers/automationEventClasses?XNAT_CSRF=' + window.csrfToken, + cache: false, + async: true, + context: this, + dataType: 'json' + }); + eventClassesAjax.done( function( data, textStatus, jqXHR ) { + if (typeof data !== 'undefined') { + XNAT.app.eventsManager.eventClasses = data; + populateEventsMenu(); + } + }); + eventClassesAjax.fail( function( data, textStatus, jqXHR ) { + xmodal.message('Error', 'An error occurred retrieving system events: ' + textStatus); + }); + } else { + populateEventsMenu(); + } + +/* url: XNAT.url.restUrl('/data/automation/events'), success: function( response ){ @@ -140,6 +200,146 @@ $(function(){ } } }); +*/ + } + + function getEventClassDisplayValueFromHandlers(_handlers, eventHandler){ + var classPart = eventHandler.srcEventClass.substring(eventHandler.srcEventClass.lastIndexOf('.')); + var matches=0; + for (var i=0; i<_handlers.length; i++) { + var classVal = _handlers[i].srcEventClass; + if (typeof classVal !== 'undefined' && classVal.endsWith(classPart) && !(eventHandler.srcEventClass == _handlers[i].srcEventClass)) { + matches++; + } + } + return (matches<1) ? classPart.substring(1) : eventHandler.srcEventClass; + } + + function getEventClassDisplayValue(ins){ + var classPart = ins.substring(ins.lastIndexOf('.')); + var displayName; + var matches=0; + for (var i=0; i<XNAT.app.eventsManager.eventClasses.length; i++) { + var classVal = XNAT.app.eventsManager.eventClasses[i].class; + if (typeof classVal !== 'undefined' && classVal.endsWith(classPart)) { + matches++; + displayName = XNAT.app.eventsManager.eventClasses[i].displayName; + } + } + return (typeof displayName !== 'undefined' && displayName !== ins) ? displayName : (matches<=1) ? classPart.substring(1) : ins; + } + + function populateEventsMenu(){ + + $('#select_eventClass').empty().append('<option></option>'); + for (var i=0; i<XNAT.app.eventsManager.eventClasses.length; i++) { + if (typeof XNAT.app.eventsManager.eventClasses[i].class !== 'undefined') { + $('#select_eventClass').append('<option value="' + XNAT.app.eventsManager.eventClasses[i].class + '">' + getEventClassDisplayValue(XNAT.app.eventsManager.eventClasses[i].class) + '</option>'); + } + } + xmodal.open({ + title: 'Add Event Handler', + template: $('#addEventHandler'), + width: 600, + height: 350, + overflow: 'auto', + beforeShow: function(obj){ + //chosenInit(obj.$modal.find('select.event, select.scriptId'), null, 300); + //obj.$modal.find('select.event, select.scriptId').chosen({ + // width: '300px', + // disable_search_threshold: 6 + //}); + }, + buttons: { + save: { + label: 'Save', + isDefault: true, + close: false, + action: doAddEventHandler + }, + close: { + label: 'Cancel' + } + } + }); + updateEventIdSelect(); + $('#select_eventClass').change(function(){ + updateEventIdSelect(); + }); + + } + + function updateEventIdSelect(){ + $('#select_event').empty().append('<option></option>'); + $('#filterRow').css('display','none'); + $('#filterDiv').html(filterableHtml); + for (var i=0; i<XNAT.app.eventsManager.eventClasses.length; i++) { + if ($('#select_eventClass').val() == XNAT.app.eventsManager.eventClasses[i].class) { + var eventIds = XNAT.app.eventsManager.eventClasses[i].eventIds; + if (typeof eventIds !== 'undefined' && eventIds.length>0) { + for (var j=0; j<eventIds.length; j++) { + var eventId = eventIds[j]; + $('#select_event').append('<option value="' + eventId + '">' + eventId + '</option>'); + } + } + var filterableFields = XNAT.app.eventsManager.eventClasses[i].filterableFields; + var filterableHtml = "" + $('#filterRow').css('display','none'); + if (typeof filterableFields !== 'undefined') { + for (var filterable in filterableFields) { + if (!filterableFields.hasOwnProperty(filterable)) continue; + var filterableVals = filterableFields[filterable]; + if (typeof filterableVals !== 'undefined' && filterableVals.length>0) { + filterableHtml = filterableHtml + '<div style="width:100%;margin-top:5px;margin-bottom:5px">' + filterable + ' <select id="filter_sel_' + filterable + '" name="' + filterable + '" class="filter">'; + filterableHtml = filterableHtml + '<option value=""><NONE></option>'; + for (var i=0; i<filterableVals.length; i++) { + filterableHtml = filterableHtml + '<option value="' + filterableVals[i] + '">' + filterableVals[i] + '</option>'; + } + filterableHtml = filterableHtml + '</select> <input type="text" id="filter_input_' + filterable + '" name="' + filterable + + '" class="filter" style="display:none" size="15"/> <button class="customButton">Custom Value</button></div>'; + } + } + } + if (filterableHtml.length>0) { + $('#filterRow').css('display','table-row'); + $('#filterDiv').html(filterableHtml); + } + $("#select_event").prop('disabled',false); + break; + } + } +/* + $(".customButton").click(function(event){ + customInputToggle(event.target); + }); +*/ + $(".customButton").each(function(){ + var eventObject = $._data(this, 'events'); + if (typeof eventObject == 'undefined' || typeof eventObject.click == 'undefined') { + $(this).click(function(event){ + customInputToggle(event.target); + }); + } + }); + $(".customButton").css('margin-left','5px'); + } + + function customInputToggle(ele){ + $(ele).parent().find("input, select").each(function() { + if ($(this).css('display') == 'none') { + $(this).css('display','inline'); + } else { + $(this).css('display','none'); + //if ($(this).is("input")) { + $(this).val(""); + //} + } + }); + if ($(ele).html() == "Selection Menu") { + $(ele).html("Custom Value"); + } else { + $(ele).html("Selection Menu"); + } } function initScriptsMenu(){ @@ -172,25 +372,70 @@ $(function(){ }); } - // initialize menus and table - initEventsMenu(); + // initialize scripts menu and table initScriptsMenu(); + if (!handlersRendered){ + initEventsTable(false); + } function doAddEventHandler( xmodalObj ){ + var filterVar = {}; + var filterEle = $("select.filter, input.filter").filter(function() { return $(this).val() != "" }); + for (var i=0; i<filterEle.length; i++) { + filterVar[filterEle[i].name]=[]; + filterVar[filterEle[i].name].push($(filterEle[i]).val()); + } + var data = { - event: xmodalObj.__modal.find('select.event').val(), + eventClass: xmodalObj.__modal.find('select.eventClass').val(), + event: xmodalObj.__modal.find('select.event, input.event').filter(function() { return $(this).val() != "" }).val(), scriptId: xmodalObj.__modal.find('select.scriptId').val(), - description: xmodalObj.__modal.find('input.description').val() + description: xmodalObj.__modal.find('input.description').val(), + filters: filterVar }; if (!data.event || data.event === '!' || !data.scriptId){ xmodal.message('Missing Information','Please select an <b>Event</b> <i>and</i> <b>Script ID</b> to create an <br>Event Handler.'); return false; } + XNAT.app.eventsManager.eventHandlerData = data; + + var eventHandlerAjax = $.ajax({ + type : "PUT", + url:serverRoot+'/data/projects/' + window.projectScope + '/automation/handlers?XNAT_CSRF=' + window.csrfToken, + cache: false, + async: true, + data: JSON.stringify(data), + contentType: 'application/json' + }); + eventHandlerAjax.done( function( data, textStatus, jqXHR ) { + if (typeof data !== 'undefined') { + xmodal.message('Success', 'Your event handler was successfully added.', 'OK', { + action: function(){ + 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(); + } + } + ); + } + }); + eventHandlerAjax.fail( function( data, textStatus, jqXHR ) { + xmodal.message('Error', 'An error occurred: [' + data.statusText + '] ' + data.responseText, 'Close', { + action: function(){ + xmodal.closeAll($(xmodal.dialog.open),$('#xmodal-manage-events')); + } + }); + }); + /* xhr.put({ - url: XNAT.url.restUrl('/data/projects/' + window.projectScope + '/automation/handlers?XNAT_CSRF=' + window.csrfToken, null, false), + url: '/data/projects/' + window.projectScope + '/automation/handlers?XNAT_CSRF=' + window.csrfToken, data: data, dataType: "json", success: function(){ @@ -215,6 +460,7 @@ $(function(){ }); } }); + */ } @@ -243,6 +489,18 @@ $(function(){ $('#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>' + + '<div id="events_manage_table" class="xnat-table" style="display:table;width:100%">' + + '<dl class="header">' + + '<dl>' + + '<dd class="col1">Event</dd>' + + '<dd class="col2">Script ID</dd>' + + '<dd class="col3">Description</dd>' + + '<dd class="col4"></dd>' + + '<dd class="col5"></dd>' + + '</dl>' + + '</dl>' + + '</div>' + /* '<table id="events_manage_table" class="xnat-table" style="display:table;width:100%">' + '<thead>' + '<th>Event</th>' + @@ -254,13 +512,14 @@ $(function(){ '<tbody>' + '</tbody>' + '</table>' + */ ); initEventsTable(true); - $("#events_manage_table").on('click', 'a.delete-handler', function(){ + $("#events_manage_table").on('click', 'button.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') + $("#events_manage_table").on('click', 'button.configure-uploader-handler', function(){ + XNAT.app.abu.configureUploaderForEventHandler($(this).data('handler'), $(this).data('event'), 'prj') }); } @@ -268,9 +527,11 @@ $(function(){ function addEventHandler(){ - var getEvents = initEventsMenu(); + initEventsMenu(); +/* + //var getEvents = initEventsMenu(); - getEvents.done(function(){ + //getEvents.done(function(){ xmodal.open({ title: 'Add Event Handler', template: $('#addEventHandler'), @@ -278,7 +539,7 @@ $(function(){ height: 300, overflow: true, beforeShow: function(obj){ - chosenInit(obj.$modal.find('select.event, select.scriptId'), null, 300); + //chosenInit(obj.$modal.find('select.event, select.scriptId'), null, 300); //obj.$modal.find('select.event, select.scriptId').chosen({ // width: '300px', // disable_search_threshold: 6 @@ -296,17 +557,34 @@ $(function(){ } } }); - }); + //}); +*/ } - function doDeleteTrigger( triggerId ){ - var url = XNAT.url.restUrl('/data/automation/triggers/' + triggerId + "?XNAT_CSRF=" + window.csrfToken); + function doDeleteTrigger(triggerId){ + var url = serverRoot+'/data/automation/triggers/' + triggerId + "?XNAT_CSRF=" + window.csrfToken; if (window.jsdebug) console.log(url); jQuery.ajax({ type: 'DELETE', url: url, cache: false, success: function(){ + var configScope; + if (typeof XNAT.app.abu.uploaderConfig !== 'undefined') { + for (var i=0; i<XNAT.app.abu.uploaderConfig.length; i++) { + var thisConfig = XNAT.app.abu.uploaderConfig[i]; + if (typeof thisConfig == 'undefined') { + continue; + } + if (thisConfig.eventTriggerId == triggerId) { + configScope = thisConfig.eventScope; + XNAT.app.abu.uploaderConfig.splice(0,1); + } + } + } + if (typeof configScope !== 'undefined') { + XNAT.app.abu.putUploaderConfiguration(configScope,false); + } xmodal.message('Success', 'The event handler was successfully deleted.', 'OK', { action: function(){ initEventsTable(); @@ -324,11 +602,11 @@ $(function(){ }); } - function deleteEventHandler( triggerId ){ + function deleteEventHandler(triggerId){ xmodal.confirm({ title: 'Delete Event Handler?', content: 'Are you sure you want to delete the handler: <br><br><b>' + triggerId + '</b>?<br><br>Only the Event Handler will be deleted. The associated Script will still be available for use.', - width: 440, + width: 560, height: 240, okLabel: 'Delete', okClose: false, // don't close yet @@ -351,3 +629,4 @@ $(function(){ $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 45ef98e089379a6b0ec97ca56b130889c3bd4b20..fbf982a8942792126a888a62168885abfecd386e 100644 --- a/src/main/webapp/scripts/xnat/app/siteEventsManager.js +++ b/src/main/webapp/scripts/xnat/app/siteEventsManager.js @@ -20,14 +20,14 @@ $(function(){ $no_event_handlers = $('#no_event_handlers'), $add_event_handler = $('#add_event_handler'), $manage_event_handlers = $('#manage_event_handlers'), - //handlersRendered = false, + handlersRendered = false, hasEvents = false; function initHandlersTable(doEdit){ if (doEdit) { - var events_manage_table = $('#events_manage_table'); + var events_manage_table = $('#events_table'); } // hide stuff @@ -46,10 +46,23 @@ $(function(){ siteEventsManager.handlers = []; if(_handlers.length) { - //handlersRendered = true; + handlersRendered = true; + eventRows += + '<dl class="header">' + + '<dl>' + + '<dd class="col1">Event</dd>' + + '<dd class="col2">Script ID</dd>' + + '<dd class="col3">Description</dd>' + + //((doEdit) ? + '<dd class="col4"></dd>' + + // '<dd class="col5"></dd>' + + // : '') + + '</dl>' + + '</dl>'; forEach(_handlers, function(eventHandler){ var _event_id = eventHandler['event']; siteEventsManager.handlers.push(_event_id); + /* eventRows += '<tr class="highlight">' + '<td class="event-id">' + _event_id + '</td>' + '<td class="script-id">' + eventHandler.scriptId + '</td>' + @@ -67,9 +80,37 @@ $(function(){ '</td>' : '' ) + '</tr>'; + */ + eventRows += '<dl class="item">' + + '<dd class="col1">' + _event_id + '</dd>' + + '<dd class="col2">' + eventHandler.scriptId + '</dd>' + + '<dd class="col3">' + eventHandler.description + '</dd>' + + //((doEdit) ? + '<dd class="col4" style="text-align: center;">' + + '<button href="javascript:" class="delete-handler event-handler-button" ' + + 'data-event="' + _event_id + '" ' + + 'data-handler="' + eventHandler.triggerId + '" title="Delete handler for event ' + _event_id + '">delete</button>' + + '</dd>' + + '<dd class="col5" style="text-align: center;">' + + '<button href="javascript:" class="configure-uploader-handler event-handler-button" ' + + 'data-event="' + _event_id + '" ' + + 'data-handler=' + eventHandler.triggerId + ' title="Configure uploader for event ' + _event_id + '">configure uploader</button>' + + '</dd>' + + //: '') + + '<dd class="colC">' + '<b>Event Class: </b> ' + getEventClassDisplayValueFromHandlers(_handlers, eventHandler) + '</dd>' + + '<dd class="colC">' + '<b>Event Filters: </b> ' + eventHandler.eventFilters + '</dd>' + + '</dl>'; + }); - $((doEdit) ? events_manage_table : $events_table).find('tbody').html(eventRows); + //$((doEdit) ? events_manage_table : $events_table).find('tbody').html(eventRows); + $((doEdit) ? events_manage_table : $events_table).html(eventRows); $((doEdit) ? events_manage_table : $events_table).show(); + $("#events_table").on('click', 'button.delete-handler', function(){ + deleteEventHandler($(this).data('handler'), $(this).data('event')) + }); + $("#events_table").on('click', 'button.configure-uploader-handler', function(){ + XNAT.app.abu.configureUploaderForEventHandler($(this).data('handler'), $(this).data('event'), 'site') + }); } else { $no_event_handlers.show(); @@ -111,6 +152,18 @@ $(function(){ $('#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>' + + '<div id="events_manage_table" class="xnat-table" style="display:table;width:100%">' + + '<dl class="header">' + + '<dl>' + + '<dd class="col1">Event</dd>' + + '<dd class="col2">Script ID</dd>' + + '<dd class="col3">Description</dd>' + + '<dd class="col4"></dd>' + + '<dd class="col5"></dd>' + + '</dl>' + + '</dl>' + + '</div>' + /* '<table id="events_manage_table" class="xnat-table" style="display:table;width:100%">' + '<thead>' + '<th>Event</th>' + @@ -122,19 +175,45 @@ $(function(){ '<tbody>' + '</tbody>' + '</table>' + */ ); initHandlersTable(true); - $("#events_manage_table").on('click', 'a.delete-handler', function(){ + $("#events_manage_table").on('click', 'button.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') + $("#events_manage_table").on('click', 'button.configure-uploader-handler', function(){ + XNAT.app.abu.configureUploaderForEventHandler($(this).data('handler'), $(this).data('event'), 'site') }); } function initEventsMenu(){ siteEventsManager.events = []; // reset array + + $("#select_event").prop('disabled','disabled'); + if (typeof XNAT.app.siteEventsManager.eventClasses === 'undefined') { + var eventClassesAjax = $.ajax({ + type : "GET", + url: serverRoot+'/xapi/eventHandlers/automationEventClasses?XNAT_CSRF=' + window.csrfToken, + cache: false, + async: true, + context: this, + dataType: 'json' + }); + eventClassesAjax.done( function( data, textStatus, jqXHR ) { + if (typeof data !== 'undefined') { + XNAT.app.siteEventsManager.eventClasses = data; + populateEventsMenu(); + } + }); + eventClassesAjax.fail( function( data, textStatus, jqXHR ) { + xmodal.message('Error', 'An error occurred retrieving system events: ' + textStatus); + }); + } else { + populateEventsMenu(); + } + + /* return xhr.getJSON({ url: XNAT.url.restUrl('/data/automation/events'), success: function( response ){ @@ -185,6 +264,147 @@ $(function(){ //} } }); + */ + } + + + function getEventClassDisplayValueFromHandlers(_handlers, eventHandler){ + var classPart = eventHandler.srcEventClass.substring(eventHandler.srcEventClass.lastIndexOf('.')); + var matches=0; + for (var i=0; i<_handlers.length; i++) { + var classVal = _handlers[i].srcEventClass; + if (typeof classVal !== 'undefined' && classVal.endsWith(classPart) && !(eventHandler.srcEventClass == _handlers[i].srcEventClass)) { + matches++; + } + } + return (matches<1) ? classPart.substring(1) : eventHandler.srcEventClass; + } + + function getEventClassDisplayValue(ins){ + var classPart = ins.substring(ins.lastIndexOf('.')); + var displayName; + var matches=0; + for (var i=0; i<XNAT.app.siteEventsManager.eventClasses.length; i++) { + var classVal = XNAT.app.siteEventsManager.eventClasses[i].class; + if (typeof classVal !== 'undefined' && classVal.endsWith(classPart)) { + matches++; + displayName = XNAT.app.siteEventsManager.eventClasses[i].displayName; + } + } + return (typeof displayName !== 'undefined' && displayName !== ins) ? displayName : (matches<=1) ? classPart.substring(1) : ins; + } + + function populateEventsMenu(){ + + $('#select_eventClass').empty().append('<option></option>'); + for (var i=0; i<XNAT.app.siteEventsManager.eventClasses.length; i++) { + if (typeof XNAT.app.siteEventsManager.eventClasses[i].class !== 'undefined') { + $('#select_eventClass').append('<option value="' + XNAT.app.siteEventsManager.eventClasses[i].class + '">' + getEventClassDisplayValue(XNAT.app.siteEventsManager.eventClasses[i].class) + '</option>'); + } + } + xmodal.open({ + title: 'Add Event Handler', + template: $('#addEventHandler'), + width: 600, + height: 350, + overflow: 'auto', + beforeShow: function(obj){ + //chosenInit(obj.$modal.find('select.event, select.scriptId'), null, 300); + //obj.$modal.find('select.event, select.scriptId').chosen({ + // width: '300px', + // disable_search_threshold: 6 + //}); + }, + buttons: { + save: { + label: 'Save', + isDefault: true, + close: false, + action: doAddEventHandler + }, + close: { + label: 'Cancel' + } + } + }); + updateEventIdSelect(); + $('#select_eventClass').change(function(){ + updateEventIdSelect(); + }); + + } + + function updateEventIdSelect(){ + $('#select_event').empty().append('<option></option>'); + $('#filterRow').css('display','none'); + $('#filterDiv').html(filterableHtml); + for (var i=0; i<XNAT.app.siteEventsManager.eventClasses.length; i++) { + if ($('#select_eventClass').val() == XNAT.app.siteEventsManager.eventClasses[i].class) { + var eventIds = XNAT.app.siteEventsManager.eventClasses[i].eventIds; + if (typeof eventIds !== 'undefined' && eventIds.length>0) { + for (var j=0; j<eventIds.length; j++) { + var eventId = eventIds[j]; + $('#select_event').append('<option value="' + eventId + '">' + eventId + '</option>'); + } + } + var filterableFields = XNAT.app.siteEventsManager.eventClasses[i].filterableFields; + var filterableHtml = "" + $('#filterRow').css('display','none'); + if (typeof filterableFields !== 'undefined') { + for (var filterable in filterableFields) { + if (!filterableFields.hasOwnProperty(filterable)) continue; + var filterableVals = filterableFields[filterable]; + if (typeof filterableVals !== 'undefined' && filterableVals.length>0) { + filterableHtml = filterableHtml + '<div style="width:100%;margin-top:5px;margin-bottom:5px">' + filterable + ' <select id="filter_sel_' + filterable + '" name="' + filterable + '" class="filter">'; + filterableHtml = filterableHtml + '<option value=""><NONE></option>'; + for (var i=0; i<filterableVals.length; i++) { + filterableHtml = filterableHtml + '<option value="' + filterableVals[i] + '">' + filterableVals[i] + '</option>'; + } + filterableHtml = filterableHtml + '</select> <input type="text" id="filter_input_' + filterable + '" name="' + filterable + + '" class="filter" style="display:none" size="15"/> <button class="customButton">Custom Value</button></div>'; + } + } + } + if (filterableHtml.length>0) { + $('#filterRow').css('display','table-row'); + $('#filterDiv').html(filterableHtml); + } + $("#select_event").prop('disabled',false); + break; + } + } +/* + $(".customButton").click(function(event){ + customInputToggle(event.target); + }); +*/ + $(".customButton").each(function(){ + var eventObject = $._data(this, 'events'); + if (typeof eventObject == 'undefined' || typeof eventObject.click == 'undefined') { + $(this).click(function(event){ + customInputToggle(event.target); + }); + } + }); + $(".customButton").css('margin-left','5px'); + } + + function customInputToggle(ele){ + $(ele).parent().find("input, select").each(function() { + if ($(this).css('display') == 'none') { + $(this).css('display','inline'); + } else { + $(this).css('display','none'); + //if ($(this).is("input")) { + $(this).val(""); + //} + } + }); + if ($(ele).html() == "Selection Menu") { + $(ele).html("Custom Value"); + } else { + $(ele).html("Selection Menu"); + } } function initScriptsMenu(){ @@ -218,15 +438,26 @@ $(function(){ } // initialize menus and table - initEventsMenu(); + if (!handlersRendered) { + initHandlersTable(false); + } initScriptsMenu(); function doAddEventHandler( xmodalObj ){ + var filterVar = {}; + var filterEle = $("select.filter, input.filter").filter(function() { return $(this).val() != "" }); + for (var i=0; i<filterEle.length; i++) { + filterVar[filterEle[i].name]=[]; + filterVar[filterEle[i].name].push($(filterEle[i]).val()); + } + var data = { - event: xmodalObj.__modal.find('select.event').val(), + eventClass: xmodalObj.__modal.find('select.eventClass').val(), + event: xmodalObj.__modal.find('select.event, input.event').filter(function() { return $(this).val() != "" }).val(), scriptId: xmodalObj.__modal.find('select.scriptId').val(), - description: xmodalObj.__modal.find('input.description').val() + description: xmodalObj.__modal.find('input.description').val(), + filters: filterVar }; // TODO: Should we let them name the trigger? Is that worthwhile? (yes) @@ -237,7 +468,41 @@ $(function(){ xmodal.message('Missing Information','Please select an <b>Event</b> <i>and</i> <b>Script ID</b> to create an <br>Event Handler.'); return false; } + XNAT.app.siteEventsManager.eventHandlerData = data; + + var eventHandlerAjax = $.ajax({ + type : "PUT", + url:serverRoot+'/data/automation/handlers?XNAT_CSRF=' + window.csrfToken, + cache: false, + async: true, + data: JSON.stringify(data), + contentType: 'application/json' + }); + eventHandlerAjax.done( function( data, textStatus, jqXHR ) { + if (typeof data !== 'undefined') { + xmodal.message('Success', 'Your event handler was successfully added.', 'OK', { + action: function(){ + initHandlersTable(false); + if ($("#events_manage_table").length>0) { + initHandlersTable(true); + } + xmodal.closeAll($(xmodal.dialog.open),$('#xmodal-manage-events')); + // Trigger automation uploader to reload handlers + XNAT.app.abu.getAutomationHandlers(); + } + } + ); + } + }); + eventHandlerAjax.fail( function( data, textStatus, jqXHR ) { + xmodal.message('Error', 'An error occurred: [' + data.statusText + '] ' + data.responseText, 'Close', { + action: function(){ + xmodal.closeAll($(xmodal.dialog.open),$('#xmodal-manage-events')); + } + }); + }); + /* xhr.put({ url: XNAT.url.restUrl('/data/automation/handlers?XNAT_CSRF=' + window.csrfToken, null, false), data: data, @@ -267,11 +532,51 @@ $(function(){ }); } }); + */ } function addEventHandler(){ + initEventsMenu(); +/* xmodal.loading.open(); initScriptsMenu(). + //done(initEventsMenu(). + done(function(){ + initEventsMenu(); + xmodal.loading.close(); + xmodal.open({ + title: 'Add Event Handler', + template: $('#addEventHandler'), + width: 500, + height: 300, + overflow: true, + esc: false, + enter: false, + beforeShow: function(obj){ + var $menus = obj.$modal.find('select.event, select.scriptId'); + $menus.trigger('chosen:updated'); + //chosenInit($menus, null, 300); + $menus.chosen({ + width: '300px', + disable_search_threshold: 6 + }); + }, + buttons: { + save: { + label: 'Save', + isDefault: true, + close: false, + action: doAddEventHandler + }, + close: { + label: 'Cancel' + } + } + }); + } + ) +*/ + /* done(initEventsMenu(). done(function(){ xmodal.loading.close(); @@ -307,10 +612,11 @@ $(function(){ } ) ); + */ } function doDeleteHandler( handlerId ){ - var url = XNAT.url.restUrl('/data/automation/triggers/' + handlerId + "?XNAT_CSRF=" + window.csrfToken, null, false); + var url = serverRoot+'/data/automation/triggers/' + handlerId + "?XNAT_CSRF=" + window.csrfToken; if (window.jsdebug) console.log(url); xhr.delete({ //type: 'DELETE', @@ -337,9 +643,8 @@ $(function(){ function deleteEventHandler( handlerId, event ){ xmodal.confirm({ title: 'Delete Event Handler?', - content: 'Are you sure you want to delete the handler for the <b>"' + event + '"</b> event? ' + - 'Only the Event Handler will be deleted. The associated Script will still be available for use.', - width: 440, + content: 'Are you sure you want to delete the handler: <br><br><b>' + handlerId + '</b>?<br><br>Only the Event Handler will be deleted. The associated Script will still be available for use.', + width: 560, height: 220, okLabel: 'Delete', okClose: false, // don't close yet @@ -354,7 +659,7 @@ $(function(){ } // removed inline onclick attributes: - $events_table.on('click', 'a.delete-handler', function(){ + $events_table.on('click', 'button.delete-handler', function(){ deleteEventHandler($(this).data('handler'), $(this).data('event')); }); diff --git a/src/main/webapp/style/uploaders/fileuploader.css b/src/main/webapp/style/uploaders/fileuploader.css index c33d6833aed5b82c9314c59d0b83778f54438466..6882f43dfcd1355987ee01fcee4e3360fec746bf 100644 --- a/src/main/webapp/style/uploaders/fileuploader.css +++ b/src/main/webapp/style/uploaders/fileuploader.css @@ -92,10 +92,10 @@ ad .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-div { position: relative; float: left; width: 430px; margin-left: 10px; } .abu-options-cb { display:block; /* or inline-block */ - width: auto; padding: 7px 0; text-align:center; margin-left:10px; float:left; + width: auto; padding: 7px 0; text-align:center; margin-left:10px; float:left; padding-top: 0px; padding-bottom: 0px; } .abu-progress { position:relative; display:inline-block; width:250px; height:15px; border: 1px solid #ddd; padding: 0px; border-radius: 0px; margin-left: 10px; } diff --git a/src/main/webapp/xnat-templates/screens/Scripts.vm b/src/main/webapp/xnat-templates/screens/Scripts.vm index 82bb9c3229aca6f33af64b3cba13ebe1bffdc8e1..dacb9a5deeedb745d1820d45d68187dfd32e17a0 100644 --- a/src/main/webapp/xnat-templates/screens/Scripts.vm +++ b/src/main/webapp/xnat-templates/screens/Scripts.vm @@ -8,6 +8,112 @@ #* @vtlvariable name="script" type="org.nrg.automation.entities.Script" *# #* @vtlvariable name="link" type="org.apache.turbine.services.pull.tools.TemplateLink" *# #* @vtlvariable name="error" type="java.lang.String" *# +<style type="text/css"> + + #events_table .item, #events_table .header, #events_manage_table .item, #events_manage_table .header { + border-bottom: 1px solid #888; + font: 11px Arial, Helvetica, sans-serif; + margin-top: 0; + margin-bottom: 0; + padding: 4px; + overflow: auto; + } + + dl.item, item.item { + display: block; + } + + #events_table dl dl, #events_manage_table dl dl { + margin: 1px 0; + } + + #events_table .header, #events_manage_table .header { + background-color: #ccc; + font-weight: bold; + } + + #events_table dl dt, #events_table dl dd, #events_manage_table dl dt, #events_manage_table dl dd { + display: block; + float: left; + padding: 4px 0; + } + + #events_table .item:hover { + background-color: #fff; + } + + #events_manage_table .item:hover { + background-color: #fff; + } + + #events_table dd, #events_manage_table dd { + margin-left: 20px; + } + + #events_table dd.col1, #events_manage_table dd.col1 { + width: 140px; + margin-left: 10px + } + + #events_table dd.col2, #events_manage_table dd.col2 { + width: 115px; + } + + #events_table dd.col3, #events_manage_table dd.col3 { + width: 230px; + } + + #events_table dd.col4, #events_manage_table dd.col4 { + width: 40px; + } + + #events_table dd.col5, #events_manage_table dd.col5 { + width: 105px; + } + + #events_table dd.col6, #events_manage_table dd.col6 { + width: 50px; + } + + #events_table dd.col7, #events_manage_table dd.col7 { + width: 130px; + } + + #events_table dd.colX, #events_manage_table dd.colX { + width: 90%; + padding-left: 70px; + } + #events_table dd.colC, #events_manage_table dd.colC { + width: 90%; + padding-left: 10px; + padding-top: 2px; + padding-bottom: 2px; + } + + #events_table dd.colEx, #events_manage_table dd.colEx { + position: relative; + } + + #events_table_title #events_manage_table_title { + font-weight: 700; + } + button.event-handler-button { + font: 11px Arial, Helvetica, sans-serif; + padding: 3px; + margin: -3px; + } + + div.row3 { + float: right; + margin-top: 8px; + } + + div.row4 { + clear: both; + margin-top: 8px; + } + +</style> <h3 style="margin:0 0 15px 0;">Automation</h3> @@ -48,8 +154,16 @@ <div id="addEventHandler" class="html-template"> <table> <tr> - <td><label for="event" class="required"><strong>Event:</strong><i>*</i></label></td> - <td><select id="select_event" name="event" class="event"></select></td> + <td><label for="eventClass" class="required"><strong>Event Type:</strong><i>*</i></label></td> + <td><select id="select_eventClass" name="eventClass" class="eventClass"></select></td> + </tr> + <tr> + <td><label for="event" class="required"><strong>Event ID:</strong><i>*</i></label></td> + <td><select id="select_event" name="event" class="event"></select> <input type="text" id="select_input" class="event" style="display:none" size="15"/> <button class="customButton">Custom Value</button></div></td> + </tr> + <tr id="filterRow"> + <td><label for="filters"><strong>Filters:</strong></label></td> + <td><div id="filterDiv"></div></td> </tr> <tr> <td><label for="scriptId" class="required"><strong>Script ID:</strong><i>*</i></label></td> @@ -108,7 +222,7 @@ <div class="yui-navset yui-navset-top bogus-tabs"> <ul class="yui-nav"> <li class="first selected"><a href="#automationEventHandlers"><em>Site Event Handlers</em></a></li> - <li><a href="#automationEvents"><em>Events</em></a></li> + ##<li><a href="#automationEvents"><em>Events</em></a></li> <li><a href="#automationScripts"><em>Scripts</em></a></li> </ul> <div class="yui-content"> @@ -121,6 +235,7 @@ <div id="events_list"> ## <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 site.</p> + <!-- <table id="events_table" class="xnat-table" style="display:none;width:100%;"> <thead> <th>Event</th> @@ -131,6 +246,9 @@ ## content populated with XNAT.app.eventsManager.initEventsTable() </tbody> </table> + --> + <div id="events_table" class="xnat-table" style="display:none;width:100%;"> + </div> <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> @@ -139,39 +257,39 @@ </div> - <!-- Events --> - <div id="automationEvents" class="yui-hidden"> - - <div id="events-container"> - - <p id="no-events" style="display:none;">There are no Events defined for this site.</p> - - <table id="events-table" class="xnat-table" style="display:none;width:100%;"> - - <thead> - <th width="40%">Event ID</th> - <th width="40%">Event Label</th> +## <!-- Events --> +## <div id="automationEvents" class="yui-hidden"> +## +## <div id="events-container"> +## +## <p id="no-events" style="display:none;">There are no Events defined for this site.</p> +## +## <table id="events-table" class="xnat-table" style="display:none;width:100%;"> +## +## <thead> +## <th width="40%">Event ID</th> +## <th width="40%">Event Label</th> ## <th>Workflow</th> - <th width="20%"> </th> - </thead> - - <tbody> - - ## events list inserted here via AJAX - - </tbody> - - </table> - - <br> - - <b style="padding:0 8px;">Define an Event from existing Workflows or create a new Event:</b> - - <button type="button" id="define-event-button" class="btn1" style="font-size:12px;" title="define an event">Define Event</button> - - </div> - - </div> +## <th width="20%"> </th> +## </thead> +## +## <tbody> +## +## ## events list inserted here via AJAX +## +## </tbody> +## +## </table> +## +## <br> +## +## <b style="padding:0 8px;">Define an Event from existing Workflows or create a new Event:</b> +## +## <button type="button" id="define-event-button" class="btn1" style="font-size:12px;" title="define an event">Define Event</button> +## +## </div> +## +## </div> <!-- Scripts --> <div id="automationScripts" class="yui-hidden"> diff --git a/src/main/webapp/xnat-templates/screens/xnat_imageSessionData/upload/xnat_imageSessionData_scans.vm b/src/main/webapp/xnat-templates/screens/xnat_imageSessionData/upload/xnat_imageSessionData_scans.vm index 02c587d218df468e13335ff64a75032c11c8a6d5..89df653c7cf6658897c1e8384b2675c8b8af8e9d 100644 --- a/src/main/webapp/xnat-templates/screens/xnat_imageSessionData/upload/xnat_imageSessionData_scans.vm +++ b/src/main/webapp/xnat-templates/screens/xnat_imageSessionData/upload/xnat_imageSessionData_scans.vm @@ -118,4 +118,4 @@ Other </tr> #end </table> -</div> \ No newline at end of file +</div> diff --git a/src/main/webapp/xnat-templates/screens/xnat_imageSessionData/xnat_imageSessionData_scans.vm b/src/main/webapp/xnat-templates/screens/xnat_imageSessionData/xnat_imageSessionData_scans.vm index ad392bdcf1d91ae438abb2a9a1f90c7e03ddfd52..b5de0812351fb963d717e848c0e53ed652866480 100644 --- a/src/main/webapp/xnat-templates/screens/xnat_imageSessionData/xnat_imageSessionData_scans.vm +++ b/src/main/webapp/xnat-templates/screens/xnat_imageSessionData/xnat_imageSessionData_scans.vm @@ -68,7 +68,7 @@ #else #renderEmptyResourceCount() #end - #if($user.canEdit($om)) + #if ($data.getSession().getAttribute("userHelper").canEdit($item)) <a class='uploadLink' style='display:none' href='' onclick='return false;' data-type='xnat:imageScanData' data-props='$!scan.getType()' data-uri='$content.getURI("/data/experiments/$om.getId()/scans/$scan.getId()")'>Upload</a> #end </td> 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 f7aa07c98d8d9e028514429b5e0a5d6a73c94daf..efbe2adf96a142b8ca0fd7798eca2bfd33df348c 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 @@ -91,21 +91,26 @@ } #pResource_exist dd.col1 { - width: 60px; + width: 40px; padding: 0px; margin-left: 10px } + dd.col1 button { + padding: 2px; + font-size: 11px; + } + #pResource_exist dd.col2 { width: 120px; } #pResource_exist dd.col3 { - width: 70px; + width: 85px; } #pResource_exist dd.col4 { - width: 70px; + width: 85px; } #pResource_exist dd.col5 { @@ -113,7 +118,7 @@ } #pResource_exist dd.col6 { - width: 50px; + width: 40px; } #pResource_exist dd.col7 { 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 5b2a918ef80e104ed0b88485b16ab93e4922788b..8d2f168f1486998b9a27deb0d1a67cf0bdf18c3e 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 @@ -40,6 +40,110 @@ border: 1px solid #d0d0d0; } + + #events_table .item, #events_table .header, #events_manage_table .item, #events_manage_table .header { + border-bottom: 1px solid #888; + font: 11px Arial, Helvetica, sans-serif; + margin-top: 0; + margin-bottom: 0; + padding: 4px; + overflow: auto; + } + + dl.item, item.item { + display: block; + } + + #events_table dl dl, #events_manage_table dl dl { + margin: 1px 0; + } + + #events_table .header, #events_manage_table .header { + background-color: #ccc; + font-weight: bold; + } + + #events_table dl dt, #events_table dl dd, #events_manage_table dl dt, #events_manage_table dl dd { + display: block; + float: left; + padding: 4px 0; + } + + #events_table .item:hover { + background-color: #fff; + } + + #events_manage_table .item:hover { + background-color: #fff; + } + + #events_table dd, #events_manage_table dd { + margin-left: 20px; + } + + #events_table dd.col1, #events_manage_table dd.col1 { + width: 140px; + margin-left: 10px + } + + #events_table dd.col2, #events_manage_table dd.col2 { + width: 115px; + } + + #events_table dd.col3, #events_manage_table dd.col3 { + width: 230px; + } + + #events_table dd.col4, #events_manage_table dd.col4 { + width: 40px; + } + + #events_table dd.col5, #events_manage_table dd.col5 { + width: 105px; + } + + #events_table dd.col6, #events_manage_table dd.col6 { + width: 50px; + } + + #events_table dd.col7, #events_manage_table dd.col7 { + width: 130px; + } + + #events_table dd.colX, #events_manage_table dd.colX { + width: 90%; + padding-left: 70px; + } + #events_table dd.colC, #events_manage_table dd.colC { + width: 90%; + padding-left: 10px; + padding-top: 2px; + padding-bottom: 2px; + } + + #events_table dd.colEx, #events_manage_table dd.colEx { + position: relative; + } + + #events_table_title #events_manage_table_title { + font-weight: 700; + } + button.event-handler-button { + font: 11px Arial, Helvetica, sans-serif; + padding: 3px; + margin: -3px; + } + + div.row3 { + float: right; + margin-top: 8px; + } + + div.row4 { + clear: both; + margin-top: 8px; + } + </style> ##<script type="text/javascript"> ## writeScripts([ @@ -57,8 +161,16 @@ <div id="addEventHandler" class="html-template"> <table> <tr> - <td><label for="event" class="required"><strong>Event:</strong><i>*</i></label></td> - <td><select id="select_event" name="event" class="event"></select></td> + <td><label for="eventClass" class="required"><strong>Event Type:</strong><i>*</i></label></td> + <td><select id="select_eventClass" name="eventClass" class="eventClass"></select></td> + </tr> + <tr> + <td><label for="event" class="required"><strong>Event ID:</strong><i>*</i></label></td> + <td><select id="select_event" name="event" class="event"></select> <input type="text" id="select_input" class="event" style="display:none" size="15"/> <button class="customButton">Custom Value</button></div></td> + </tr> + <tr id="filterRow"> + <td><label for="filters"><strong>Filters:</strong></label></td> + <td><div id="filterDiv"></div></td> </tr> <tr> <td><label for="scriptId" class="required"><strong>Script ID:</strong><i>*</i></label></td> @@ -364,6 +476,7 @@ <div id="events_list" style="min-height:120px;"> <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_table" class="xnat-table" style="display:none;width:100%;"> <thead> <th>Event</th> @@ -374,6 +487,9 @@ ## content populated with XNAT.app.eventsManager.initEventsTable() </tbody> </table> + --> + <div id="events_table" class="xnat-table" style="display:none;width:100%;"> + </div> <br> <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;">