diff --git a/build.gradle b/build.gradle index bc56306ddd203e46215ffa78aa4282df7b61bf81..ec3573df40a56479ac853add71a98f0f9ab1e4bf 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ def vXnat = '1.7.0-SNAPSHOT' def vXnatPipeline = vXnat def vXImgView = '1.0.0-SNAPSHOT' -def vSpring = '4.2.5.RELEASE' +def vSpring = '4.2.7.RELEASE' def vSpringSecurity = '4.0.4.RELEASE' def vSwagger = '2.4.0' def vHibernate = '4.3.11.Final' @@ -41,7 +41,7 @@ buildscript { dependencies { classpath "com.bmuschko:gradle-cargo-plugin:2.2.2" classpath "com.bmuschko:gradle-tomcat-plugin:2.2.4" - classpath "gradle.plugin.com.palantir.gradle.gitversion:gradle-git-version:0.5.1" + classpath "gradle.plugin.com.palantir.gradle.gitversion:gradle-git-version:0.5.2" } } diff --git a/src/main/java/org/nrg/dcm/DicomSCPManager.java b/src/main/java/org/nrg/dcm/DicomSCPManager.java index f91b8e5d296ca5a810478368de25fe9b8071e5fb..f7d91ef50ccf2ad201e4beb4ce76de88d4da1ef4 100644 --- a/src/main/java/org/nrg/dcm/DicomSCPManager.java +++ b/src/main/java/org/nrg/dcm/DicomSCPManager.java @@ -10,6 +10,7 @@ */ package org.nrg.dcm; +import org.nrg.dcm.exceptions.EnabledDICOMReceiverWithDuplicatePortException; import org.nrg.dcm.preferences.DicomSCPInstance; import org.nrg.dcm.preferences.DicomSCPPreference; import org.nrg.framework.exceptions.NrgServiceError; @@ -25,7 +26,6 @@ import java.io.IOException; import java.util.*; public class DicomSCPManager { - @Autowired public DicomSCPManager(final DicomSCPPreference dicomScpPreferences, final SiteConfigPreferences siteConfigPreferences) { _dicomScpPreferences = dicomScpPreferences; @@ -38,17 +38,17 @@ public class DicomSCPManager { stopDicomSCPs(); } - public DicomSCP create(final DicomSCPInstance instance) throws NrgServiceException { + public DicomSCP create(final DicomSCPInstance instance) throws IOException, EnabledDICOMReceiverWithDuplicatePortException { instance.setId(getNextKey()); - try { - _dicomScpPreferences.setDicomSCPInstance(instance); - if (_log.isDebugEnabled()) { - _log.debug("Created new DICOM SCP: " + instance.toString()); - } - return _dicomScpPreferences.getDicomSCP(instance.getId()); - } catch (IOException e) { - throw new NrgServiceRuntimeException(NrgServiceError.Unknown, "Unable to create DICOM SCP: " + instance.getAeTitle() + ":" + instance.getPort(), e); + _dicomScpPreferences.setDicomSCPInstance(instance); + if (_log.isDebugEnabled()) { + _log.debug("Created new DICOM SCP: " + instance.toString()); + } + final DicomSCP dicomSCP = _dicomScpPreferences.getDicomSCP(instance.getId()); + if (instance.isEnabled()) { + dicomSCP.start(); } + return dicomSCP; } public void delete(final int id) throws NrgServiceException { @@ -61,11 +61,27 @@ public class DicomSCPManager { _dicomScpPreferences.deleteDicomSCPInstance(id); } + public boolean hasDicomSCP(final int id) { + return _dicomScpPreferences.hasDicomSCPInstance(id); + } + + public Map<DicomSCP, Boolean> areDicomSCPsStarted() { + final Map<DicomSCP, Boolean> statuses = new HashMap<>(); + for (final DicomSCP dicomSCP : _dicomScpPreferences.getDicomSCPs()) { + statuses.put(dicomSCP, dicomSCP.isStarted()); + } + return statuses; + } + public List<DicomSCPInstance> getDicomSCPInstances() { return new ArrayList<>(_dicomScpPreferences.getDicomSCPInstances().values()); } - public void setDicomSCPInstance(final DicomSCPInstance instance) { + public DicomSCPInstance getDicomSCPInstance(final int id) { + return _dicomScpPreferences.getDicomSCPInstance(id); + } + + public void setDicomSCPInstance(final DicomSCPInstance instance) throws EnabledDICOMReceiverWithDuplicatePortException { try { _dicomScpPreferences.setDicomSCPInstance(instance); } catch (IOException e) { @@ -82,21 +98,41 @@ public class DicomSCPManager { final List<DicomSCP> started = new ArrayList<>(); for (final DicomSCPInstance instance : _dicomScpPreferences.getDicomSCPInstances().values()) { if (instance.isEnabled()) { - started.add(startDicomSCP(instance)); + final DicomSCP dicomSCP = startDicomSCP(instance); + if (dicomSCP != null) { + started.add(dicomSCP); + } } } return started; } - public void startDicomSCP(final int id) { - startDicomSCP(_dicomScpPreferences.getDicomSCPInstance(id)); + public DicomSCP startDicomSCP(final int id) { + final DicomSCPInstance instance = _dicomScpPreferences.getDicomSCPInstance(id); + if (instance == null) { + throw new NrgServiceRuntimeException(NrgServiceError.UnknownEntity, "Couldn't find the DICOM SCP instance identified by " + id); + } + return startDicomSCP(instance); + } + + public DicomSCP startDicomSCP(final DicomSCPInstance instance) { + try { + final DicomSCP dicomSCP = _dicomScpPreferences.getDicomSCP(instance.getId()); + if (!dicomSCP.isStarted()) { + dicomSCP.start(); + return dicomSCP; + } + return null; + } catch (IOException e) { + throw new NrgServiceRuntimeException(NrgServiceError.Unknown, "Unable to start DICOM SCP: " + instance.getAeTitle() + ":" + instance.getPort(), e); + } } public List<DicomSCP> stopDicomSCPs() { final List<DicomSCP> stopped = new ArrayList<>(); - for (final DicomSCP dicomSCP : _dicomScpPreferences.getDicomSCPs()) { - if (dicomSCP.isStarted()) { - dicomSCP.stop(); + for (final DicomSCPInstance instance : _dicomScpPreferences.getDicomSCPInstances().values()) { + final DicomSCP dicomSCP = stopDicomSCP(instance); + if (dicomSCP != null) { stopped.add(dicomSCP); } } @@ -108,19 +144,25 @@ public class DicomSCPManager { if (instance == null) { throw new NrgServiceRuntimeException(NrgServiceError.UnknownEntity, "Couldn't find the DICOM SCP instance identified by " + id); } + stopDicomSCP(instance); + } + + public DicomSCP stopDicomSCP(final DicomSCPInstance instance) { try { - final DicomSCP dicomSCP = _dicomScpPreferences.getDicomSCP(id); + final DicomSCP dicomSCP = _dicomScpPreferences.getDicomSCP(instance.getId()); if (dicomSCP != null) { if (dicomSCP.isStarted()) { dicomSCP.stop(); + return dicomSCP; } } + return null; } catch (IOException e) { throw new NrgServiceRuntimeException(NrgServiceError.Unknown, "Unable to stop DICOM SCP: " + instance.getAeTitle() + ":" + instance.getPort(), e); } } - public void enableDicomSCP(final int id) { + public void enableDicomSCP(final int id) throws EnabledDICOMReceiverWithDuplicatePortException { final DicomSCPInstance instance = _dicomScpPreferences.getDicomSCPInstance(id); try { if (!instance.isEnabled()) { @@ -149,34 +191,13 @@ public class DicomSCPManager { } } catch (IOException e) { throw new NrgServiceRuntimeException(NrgServiceError.Unknown, "Unable to disable DICOM SCP: " + instance.getAeTitle() + ":" + instance.getPort(), e); + } catch (EnabledDICOMReceiverWithDuplicatePortException ignored) { + // We can ignore this: the exception comes when an enabled instance is inserted with the same port as + // another enabled instance. Since we're explicitly disabling this instance, we won't actually get this + // error. } } - public Map<DicomSCP, Boolean> areDicomSCPsStarted() { - final Map<DicomSCP, Boolean> statuses = new HashMap<>(); - for (final DicomSCP dicomSCP : _dicomScpPreferences.getDicomSCPs()) { - statuses.put(dicomSCP, dicomSCP.isStarted()); - } - return statuses; - } - - public boolean hasDicomSCP(final int id) { - return _dicomScpPreferences.hasDicomSCPInstance(id); - } - - public DicomSCPInstance getDicomSCPInstance(final int id) { - return _dicomScpPreferences.getDicomSCPInstance(id); - } - - private DicomSCP startDicomSCP(final DicomSCPInstance instance) { - try { - final DicomSCP dicomSCP = _dicomScpPreferences.getDicomSCP(instance.getId()); - dicomSCP.start(); - return dicomSCP; - } catch (IOException e) { - throw new NrgServiceRuntimeException(NrgServiceError.Unknown, "Unable to start DICOM SCP: " + instance.getAeTitle() + ":" + instance.getPort(), e); - } - } private int getNextKey() { final Set<String> keys = _dicomScpPreferences.getDicomSCPInstances().keySet(); diff --git a/src/main/java/org/nrg/dcm/exceptions/EnabledDICOMReceiverWithDuplicatePortException.java b/src/main/java/org/nrg/dcm/exceptions/EnabledDICOMReceiverWithDuplicatePortException.java new file mode 100644 index 0000000000000000000000000000000000000000..9effe5bd5192656b5c980da2d9da6980737fe3b2 --- /dev/null +++ b/src/main/java/org/nrg/dcm/exceptions/EnabledDICOMReceiverWithDuplicatePortException.java @@ -0,0 +1,29 @@ +package org.nrg.dcm.exceptions; + +import org.nrg.dcm.preferences.DicomSCPInstance; +import org.nrg.framework.exceptions.NrgServiceError; +import org.nrg.framework.exceptions.NrgServiceException; + +public class EnabledDICOMReceiverWithDuplicatePortException extends NrgServiceException { + public EnabledDICOMReceiverWithDuplicatePortException(final DicomSCPInstance existing, final DicomSCPInstance inserted) { + super(NrgServiceError.AlreadyInitialized); + _existing = existing; + _inserted = inserted; + } + + public DicomSCPInstance getExisting() { + return _existing; + } + + public DicomSCPInstance getInserted() { + return _inserted; + } + + @Override + public String toString() { + return "Tried to insert DICOM SCP receiver ID " + _inserted.getId() + " [" + _inserted.toString() + "], which replicated the port as the enabled receiver ID " + _existing.getId() + " [" + _existing.toString() + "]"; + } + + private final DicomSCPInstance _existing; + private final DicomSCPInstance _inserted; +} diff --git a/src/main/java/org/nrg/dcm/preferences/DicomSCPInstance.java b/src/main/java/org/nrg/dcm/preferences/DicomSCPInstance.java index 545d8b3016977428f0290ce712b331450afb943e..2d72c19fcca444b9e47544704bdf190de6c2573f 100644 --- a/src/main/java/org/nrg/dcm/preferences/DicomSCPInstance.java +++ b/src/main/java/org/nrg/dcm/preferences/DicomSCPInstance.java @@ -41,7 +41,6 @@ public class DicomSCPInstance { setEnabled(enabled); } - public int getId() { return _id; } @@ -92,14 +91,7 @@ public class DicomSCPInstance { @Override public String toString() { - return "DicomSCPInstance{" + - "_id='" + _id + '\'' + - ", _port=" + _port + - ", _aeTitle='" + _aeTitle + '\'' + - ", _identifier='" + _identifier + '\'' + - ", _fileNamer='" + _fileNamer + '\'' + - ", _enabled='" + _enabled + '\'' + - '}'; + return _aeTitle + ":" + _port; } private int _id; diff --git a/src/main/java/org/nrg/dcm/preferences/DicomSCPPreference.java b/src/main/java/org/nrg/dcm/preferences/DicomSCPPreference.java index 1fd53a1eec983b4e1f6501e39d179535801f0cfb..de3cf97330fb62373932a1ae48f416b2cfee6b22 100644 --- a/src/main/java/org/nrg/dcm/preferences/DicomSCPPreference.java +++ b/src/main/java/org/nrg/dcm/preferences/DicomSCPPreference.java @@ -3,6 +3,7 @@ package org.nrg.dcm.preferences; import org.apache.commons.lang3.StringUtils; import org.nrg.dcm.DicomFileNamer; import org.nrg.dcm.DicomSCP; +import org.nrg.dcm.exceptions.EnabledDICOMReceiverWithDuplicatePortException; import org.nrg.framework.exceptions.NrgServiceError; import org.nrg.framework.exceptions.NrgServiceRuntimeException; import org.nrg.prefs.annotations.NrgPreference; @@ -27,8 +28,8 @@ import java.util.concurrent.Executors; @NrgPreferenceBean(toolId = "dicomScpManager", toolName = "DICOM SCP Manager", description = "Manages configuration of the various DICOM SCP endpoints on the XNAT system.") public class DicomSCPPreference extends AbstractPreferenceBean { @Autowired - public DicomSCPPreference(final XnatUserProvider provider, final ApplicationContext context) { - _provider = provider; + public DicomSCPPreference(final XnatUserProvider primaryAdminUserProvider, final ApplicationContext context) { + _provider = primaryAdminUserProvider; _context = context; } @@ -36,6 +37,37 @@ public class DicomSCPPreference extends AbstractPreferenceBean { return getDicomSCPInstances().containsKey(Integer.toString(id)); } + @SuppressWarnings("unused") + public List<DicomSCPInstance> getDicomSCPAtAETitle(final String aeTitle) { + final List<DicomSCPInstance> found = new ArrayList<>(); + for (final DicomSCPInstance instance : getDicomSCPInstances().values()) { + if (StringUtils.equals(aeTitle, instance.getAeTitle())) { + found.add(instance); + } + } + return found; + } + + public DicomSCPInstance getDicomSCPAtPort(final int port) { + for (final DicomSCPInstance instance : getDicomSCPInstances().values()) { + if (port == instance.getPort() && instance.isEnabled()) { + return instance; + } + } + return null; + } + + @SuppressWarnings("unused") + public List<DicomSCPInstance> getAllDicomSCPsAtPort(final int port) { + final List<DicomSCPInstance> found = new ArrayList<>(); + for (final DicomSCPInstance instance : getDicomSCPInstances().values()) { + if (port == instance.getPort() && instance.isEnabled()) { + found.add(instance); + } + } + return found; + } + @NrgPreference(defaultValue = "{'1': {'id': '1', 'aeTitle': 'XNAT', 'port': 8104, 'enabled': true}}", key = "id") public Map<String, DicomSCPInstance> getDicomSCPInstances() { return getMapValue(PREF_ID); @@ -45,9 +77,18 @@ public class DicomSCPPreference extends AbstractPreferenceBean { return getDicomSCPInstances().get(Integer.toString(id)); } - public void setDicomSCPInstance(final DicomSCPInstance instance) throws IOException { + public void setDicomSCPInstance(final DicomSCPInstance instance) throws IOException, EnabledDICOMReceiverWithDuplicatePortException { final int id = instance.getId(); - deleteDicomSCP(id); + + final DicomSCPInstance atPort = getDicomSCPAtPort(instance.getPort()); + if (atPort != null && atPort.getId() != id) { + throw new EnabledDICOMReceiverWithDuplicatePortException(atPort, instance); + } + + if (hasDicomSCPInstance(id)) { + deleteDicomSCP(id); + } + try { set(serialize(instance), PREF_ID, Integer.toString(id)); } catch (InvalidPreferenceName invalidPreferenceName) { diff --git a/src/main/java/org/nrg/xapi/model/event/EventClassInfo.java b/src/main/java/org/nrg/xapi/model/event/EventClassInfo.java index d8f826b4f4eb25ed225e933da7d14ab8271e39a6..49d4f5759211d997f12815d0b2028db5e5e33216 100644 --- a/src/main/java/org/nrg/xapi/model/event/EventClassInfo.java +++ b/src/main/java/org/nrg/xapi/model/event/EventClassInfo.java @@ -31,7 +31,7 @@ public class EventClassInfo { String _description; /** The _filterable fields. */ - final Map<String,List<String>> _filterableFields = Maps.newHashMap(); + final Map<String,EventHandlerFilterInfo> _filterableFields = Maps.newHashMap(); /** The _event ids. */ final List<String> _eventIds = Lists.newArrayList(); @@ -124,7 +124,7 @@ public class EventClassInfo { */ @ApiModelProperty(value = "Map of Filterable fields.") @JsonProperty("filterableFields") - public Map<String,List<String>> getFilterableFieldsMap() { + public Map<String,EventHandlerFilterInfo> getFilterableFieldsMap() { return _filterableFields; } diff --git a/src/main/java/org/nrg/xapi/model/event/EventHandlerFilterInfo.java b/src/main/java/org/nrg/xapi/model/event/EventHandlerFilterInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..59deaa003b3b7eb343d92c4c1cfac7f73303b46f --- /dev/null +++ b/src/main/java/org/nrg/xapi/model/event/EventHandlerFilterInfo.java @@ -0,0 +1,74 @@ +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.framework.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 EventHandlerFilterInfo { + + String _defaultValue; + + List<String> _filterValues; + + Boolean _filterRequired; + + Boolean _includeValuesFromDatabase; + + + @ApiModelProperty(value = "Default selected value for event filter values") + @JsonProperty("defaultValue") + + public String getDefaultValue() { + return _defaultValue; + } + + public void setDefaultValue(String _defaultValue) { + this._defaultValue = _defaultValue; + } + + @ApiModelProperty(value = "Filter Values") + @JsonProperty("filterValues") + public List<String> getFilterValues() { + return _filterValues; + } + + public void setFilterValues(List<String> _filterValues) { + this._filterValues = _filterValues; + } + + @ApiModelProperty(value = "Filter Required?") + @JsonProperty("filterRequired") + public Boolean getFilterRequired() { + return _filterRequired; + } + + public void setFilterRequired(Boolean _filterRequired) { + this._filterRequired = _filterRequired; + } + + @ApiModelProperty(value = "Include Values From Database?") + @JsonProperty("includeValuesFromDatabase") + public Boolean getIncludeValuesFromDatabase() { + return _includeValuesFromDatabase; + } + + public void setIncludeValuesFromDatabase(Boolean _includeValuesFromDatabase) { + this._includeValuesFromDatabase = _includeValuesFromDatabase; + } + +} diff --git a/src/main/java/org/nrg/xapi/rest/dicomscp/DicomSCPApi.java b/src/main/java/org/nrg/xapi/rest/dicomscp/DicomSCPApi.java index ce166f5dea24770375e60c0fc4ab2d24be98680c..ece0cd4d31679efa9b193628314fdc9ee0453b3c 100644 --- a/src/main/java/org/nrg/xapi/rest/dicomscp/DicomSCPApi.java +++ b/src/main/java/org/nrg/xapi/rest/dicomscp/DicomSCPApi.java @@ -3,11 +3,14 @@ package org.nrg.xapi.rest.dicomscp; import io.swagger.annotations.*; import org.nrg.dcm.DicomSCP; import org.nrg.dcm.DicomSCPManager; +import org.nrg.dcm.exceptions.EnabledDICOMReceiverWithDuplicatePortException; import org.nrg.dcm.preferences.DicomSCPInstance; import org.nrg.framework.annotations.XapiRestController; import org.nrg.framework.exceptions.NrgServiceException; import org.nrg.xapi.rest.NotFoundException; import org.nrg.xdat.rest.AbstractXapiRestController; +import org.nrg.xdat.security.services.RoleHolder; +import org.nrg.xdat.security.services.UserManagementServiceI; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -16,29 +19,43 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.io.IOException; import java.util.List; @Api(description = "XNAT DICOM SCP management API") @XapiRestController @RequestMapping(value = "/dicomscp") public class DicomSCPApi extends AbstractXapiRestController { - @Autowired - public DicomSCPApi(final DicomSCPManager manager) { + public DicomSCPApi(final DicomSCPManager manager, final UserManagementServiceI userManagementService, final RoleHolder roleHolder) { + super(userManagementService, roleHolder); _manager = manager; } @ApiOperation(value = "Get list of all configured DICOM SCP receiver definitions.", notes = "The primary DICOM SCP retrieval function returns a list of all DICOM SCP receivers defined for the current system.", response = DicomSCPInstance.class, responseContainer = "List") @ApiResponses({@ApiResponse(code = 200, message = "A list of DICOM SCP receiver definitions."), @ApiResponse(code = 500, message = "Unexpected error")}) - @RequestMapping(produces = {MediaType.APPLICATION_JSON_VALUE}, method = RequestMethod.GET) + @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET) @ResponseBody public ResponseEntity<List<DicomSCPInstance>> dicomSCPsGet() { return new ResponseEntity<>(_manager.getDicomSCPInstances(), HttpStatus.OK); } + @ApiOperation(value = "Creates a new DICOM SCP receiver from the request body.", notes = "The newly created DICOM SCP receiver instance is returned from the call. This should include the instance ID for the new object.", response = DicomSCPInstance.class) + @ApiResponses({@ApiResponse(code = 200, message = "The newly created DICOM SCP receiver definition."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to view this DICOM SCP receiver definition."), @ApiResponse(code = 409, message = "A DICOM SCP receiver already exists and is enabled at the same port."), @ApiResponse(code = 500, message = "Unexpected error")}) + @RequestMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.POST) + @ResponseBody + public ResponseEntity<DicomSCPInstance> dicomSCPCreate(@RequestBody final DicomSCPInstance instance) throws IOException, EnabledDICOMReceiverWithDuplicatePortException { + HttpStatus status = isPermitted(); + if (status != null) { + return new ResponseEntity<>(status); + } + _manager.create(instance); + return new ResponseEntity<>(instance, HttpStatus.OK); + } + @ApiOperation(value = "Gets the DICOM SCP receiver definition with the specified ID.", notes = "Returns the DICOM SCP receiver definition with the specified ID.", response = DicomSCPInstance.class) @ApiResponses({@ApiResponse(code = 200, message = "DICOM SCP receiver definition successfully retrieved."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to view this DICOM SCP receiver definition."), @ApiResponse(code = 404, message = "DICOM SCP receiver definition not found."), @ApiResponse(code = 500, message = "Unexpected error")}) - @RequestMapping(value = {"/{id}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET}) + @RequestMapping(value = {"{id}"}, produces = MediaType.APPLICATION_JSON_VALUE, method = {RequestMethod.GET}) public ResponseEntity<DicomSCPInstance> dicomSCPInstanceGet(@ApiParam(value = "ID of the DICOM SCP receiver definition to fetch", required = true) @PathVariable("id") final int id) { HttpStatus status = isPermitted(); if (status != null) { @@ -48,24 +65,24 @@ public class DicomSCPApi extends AbstractXapiRestController { : new ResponseEntity<DicomSCPInstance>(HttpStatus.NOT_FOUND); } - @ApiOperation(value = "Creates or updates the DICOM SCP receiver definition object with the specified ID.", notes = "Returns the updated DICOM SCP receiver definition.", response = DicomSCPInstance.class) - @ApiResponses({@ApiResponse(code = 200, message = "DICOM SCP receiver definition successfully created or updated."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to create or update this DICOM SCP receiver definition."), @ApiResponse(code = 404, message = "DICOM SCP receiver definition not found."), @ApiResponse(code = 500, message = "Unexpected error")}) - @RequestMapping(value = {"/{id}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT}) - public ResponseEntity<DicomSCPInstance> dicomSCPInstanceCreateOrUpdate(@ApiParam(value = "The ID of the DICOM SCP receiver definition to create or update.", required = true) @PathVariable("id") final int id, @RequestBody final DicomSCPInstance instance) throws NotFoundException { + @ApiOperation(value = "Updates the DICOM SCP receiver definition object with the specified ID.", notes = "Returns the updated DICOM SCP receiver definition.", response = DicomSCPInstance.class) + @ApiResponses({@ApiResponse(code = 200, message = "DICOM SCP receiver definition successfully updated."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to create or update this DICOM SCP receiver definition."), @ApiResponse(code = 404, message = "DICOM SCP receiver definition not found."), @ApiResponse(code = 500, message = "Unexpected error")}) + @RequestMapping(value = {"{id}"}, produces = MediaType.APPLICATION_JSON_VALUE, method = {RequestMethod.PUT}) + public ResponseEntity<DicomSCPInstance> dicomSCPInstanceCreateOrUpdate(@ApiParam(value = "The ID of the DICOM SCP receiver definition to update.", required = true) @PathVariable("id") final int id, @RequestBody final DicomSCPInstance instance) throws NotFoundException, EnabledDICOMReceiverWithDuplicatePortException { HttpStatus status = isPermitted(); if (status != null) { return new ResponseEntity<>(status); } - if (!_manager.hasDicomSCP(id)) { - try { - _manager.create(instance); - } catch (NrgServiceException e) { - _log.error("An error occurred trying to create a new DICOM SCP instance", e); - return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); - } - } else { + + if (_manager.hasDicomSCP(id)) { + // Set the ID to the value specified in the REST call. If ID not specified on PUT, value will be zero, so we + // need to make sure it's set to the proper value. If they submit it under the wrong ID well... + instance.setId(id); _manager.setDicomSCPInstance(instance); + } else { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); } + if(instance.isEnabled()){ _manager.startDicomSCP(id); } @@ -77,7 +94,7 @@ public class DicomSCPApi extends AbstractXapiRestController { @ApiOperation(value = "Deletes the DICOM SCP receiver definition object with the specified ID.", notes = "This call will stop the receiver if it's currently running.", response = Void.class) @ApiResponses({@ApiResponse(code = 200, message = "DICOM SCP receiver definition successfully created or updated."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to delete this DICOM SCP receiver definition."), @ApiResponse(code = 404, message = "DICOM SCP receiver definition not found."), @ApiResponse(code = 500, message = "Unexpected error")}) - @RequestMapping(value = {"/{id}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.DELETE}) + @RequestMapping(value = {"{id}"}, produces = MediaType.APPLICATION_JSON_VALUE, method = {RequestMethod.DELETE}) public ResponseEntity<Void> dicomSCPInstanceDelete(@ApiParam(value = "The ID of the DICOM SCP receiver definition to delete.", required = true) @PathVariable("id") final int id) throws NotFoundException { HttpStatus status = isPermitted(); if (status != null) { @@ -97,7 +114,7 @@ public class DicomSCPApi extends AbstractXapiRestController { @ApiOperation(value = "Returns whether the DICOM SCP receiver definition with the specified ID is enabled.", notes = "Returns true or false based on whether the specified DICOM SCP receiver definition is enabled or not.", response = Boolean.class) @ApiResponses({@ApiResponse(code = 200, message = "DICOM SCP receiver definition enabled status successfully retrieved."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to view this DICOM SCP receiver definition."), @ApiResponse(code = 404, message = "DICOM SCP receiver definition not found."), @ApiResponse(code = 500, message = "Unexpected error")}) - @RequestMapping(value = {"/{id}/enabled"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET}) + @RequestMapping(value = {"{id}/enabled"}, produces = MediaType.APPLICATION_JSON_VALUE, method = {RequestMethod.GET}) public ResponseEntity<Boolean> dicomSCPInstanceEnabledGet(@ApiParam(value = "The ID of the DICOM SCP receiver definition to retrieve the enabled status for.", required = true) @PathVariable("id") final int id) { HttpStatus status = isPermitted(); if (status != null) { @@ -109,17 +126,17 @@ public class DicomSCPApi extends AbstractXapiRestController { @ApiOperation(value = "Sets the DICOM SCP receiver definition's enabled state.", notes = "Sets the enabled state of the DICOM SCP receiver definition with the specified ID to the value of the flag parameter.", response = Void.class) @ApiResponses({@ApiResponse(code = 200, message = "DICOM SCP receiver definition enabled status successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to enable or disable this DICOM SCP receiver definition."), @ApiResponse(code = 404, message = "DICOM SCP receiver definition not found."), @ApiResponse(code = 500, message = "Unexpected error")}) - @RequestMapping(value = {"/{id}/enabled/{flag}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT}) + @RequestMapping(value = {"{id}/enabled/{flag}"}, produces = MediaType.APPLICATION_JSON_VALUE, method = {RequestMethod.PUT}) public ResponseEntity<Void> dicomSCPInstanceSetEnabledFlag(@ApiParam(value = "ID of the DICOM SCP receiver definition to modify", required = true) @PathVariable("id") final int id, - @ApiParam(value = "The value to set for the enabled status.", required = true) @PathVariable("flag") final Boolean flag) { - HttpStatus status = isPermitted(); + @ApiParam(value = "The value to set for the enabled status.", required = true) @PathVariable("flag") final Boolean flag) throws EnabledDICOMReceiverWithDuplicatePortException { + final HttpStatus status = isPermitted(); if (status != null) { return new ResponseEntity<>(status); } if (!_manager.hasDicomSCP(id)) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } - DicomSCPInstance instanceToChange = _manager.getDicomSCPInstance(id); + final DicomSCPInstance instanceToChange = _manager.getDicomSCPInstance(id); instanceToChange.setEnabled(flag); _manager.setDicomSCPInstance(instanceToChange); @@ -134,7 +151,7 @@ public class DicomSCPApi extends AbstractXapiRestController { @ApiOperation(value = "Starts all enabled DICOM SCP receivers.", notes = "This starts all enabled DICOM SCP receivers. The return value contains the definitions of all of the started receivers.", responseContainer = "List", response = DicomSCP.class) @ApiResponses({@ApiResponse(code = 200, message = "DICOM SCP receivers successfully started."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to enable or disable this DICOM SCP receiver definition."), @ApiResponse(code = 404, message = "DICOM SCP receiver definition not found."), @ApiResponse(code = 500, message = "Unexpected error")}) - @RequestMapping(value = {"/start"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT}) + @RequestMapping(value = {"start"}, produces = MediaType.APPLICATION_JSON_VALUE, method = {RequestMethod.PUT}) public ResponseEntity<List<DicomSCP>> dicomSCPInstancesStart() { HttpStatus status = isPermitted(); if (status != null) { @@ -145,7 +162,7 @@ public class DicomSCPApi extends AbstractXapiRestController { @ApiOperation(value = "Starts the DICOM SCP receiver.", notes = "This starts the DICOM SCP receiver. Note that this will start the receiver regardless of its enabled or disabled setting. This returns true if the instance was started and false if not.", response = Boolean.class) @ApiResponses({@ApiResponse(code = 200, message = "DICOM SCP receiver successfully started."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to enable or disable this DICOM SCP receiver definition."), @ApiResponse(code = 404, message = "DICOM SCP receiver definition not found."), @ApiResponse(code = 500, message = "Unexpected error")}) - @RequestMapping(value = {"/{id}/start"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT}) + @RequestMapping(value = {"{id}/start"}, produces = MediaType.APPLICATION_JSON_VALUE, method = {RequestMethod.PUT}) public ResponseEntity<Boolean> dicomSCPInstanceStart(@ApiParam(value = "ID of the DICOM SCP receiver to start.", required = true) @PathVariable("id") final int id) { HttpStatus status = isPermitted(); if (status != null) { @@ -160,7 +177,7 @@ public class DicomSCPApi extends AbstractXapiRestController { @ApiOperation(value = "Stops all enabled DICOM SCP receivers.", notes = "This stops all enabled DICOM SCP receivers. The return value contains the definitions of all of the started receivers.", responseContainer = "List", response = DicomSCP.class) @ApiResponses({@ApiResponse(code = 200, message = "DICOM SCP receivers successfully stopped."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to enable or disable this DICOM SCP receiver definition."), @ApiResponse(code = 404, message = "DICOM SCP receiver definition not found."), @ApiResponse(code = 500, message = "Unexpected error")}) - @RequestMapping(value = {"/stop"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT}) + @RequestMapping(value = {"stop"}, produces = MediaType.APPLICATION_JSON_VALUE, method = {RequestMethod.PUT}) public ResponseEntity<List<DicomSCP>> dicomSCPInstancesStop() { HttpStatus status = isPermitted(); if (status != null) { @@ -171,7 +188,7 @@ public class DicomSCPApi extends AbstractXapiRestController { @ApiOperation(value = "Stops the DICOM SCP receiver.", notes = "This stops the DICOM SCP receiver. Note that this will stop the receiver regardless of its enabled or disabled setting. This returns true if the instance was stopped and false if not.", response = Boolean.class) @ApiResponses({@ApiResponse(code = 200, message = "DICOM SCP receiver successfully stopped."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to enable or disable this DICOM SCP receiver definition."), @ApiResponse(code = 404, message = "DICOM SCP receiver definition not found."), @ApiResponse(code = 500, message = "Unexpected error")}) - @RequestMapping(value = {"/{id}/stop"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT}) + @RequestMapping(value = {"{id}/stop"}, produces = MediaType.APPLICATION_JSON_VALUE, method = {RequestMethod.PUT}) public ResponseEntity<Boolean> dicomSCPInstanceStop(@ApiParam(value = "ID of the DICOM SCP receiver to stop.", required = true) @PathVariable("id") final int id) { HttpStatus status = isPermitted(); if (status != null) { diff --git a/src/main/java/org/nrg/xapi/rest/dicomscp/DicomSCPApiAdvice.java b/src/main/java/org/nrg/xapi/rest/dicomscp/DicomSCPApiAdvice.java new file mode 100644 index 0000000000000000000000000000000000000000..910e7a4008550bb7b077e47ac9bdbcbebd0dd97e --- /dev/null +++ b/src/main/java/org/nrg/xapi/rest/dicomscp/DicomSCPApiAdvice.java @@ -0,0 +1,21 @@ +package org.nrg.xapi.rest.dicomscp; + +import org.nrg.dcm.exceptions.EnabledDICOMReceiverWithDuplicatePortException; +import org.nrg.framework.exceptions.NrgServiceException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@ControllerAdvice +public class DicomSCPApiAdvice { + @ExceptionHandler(EnabledDICOMReceiverWithDuplicatePortException.class) + public ResponseEntity<String> handleEnabledDICOMReceiverWithDuplicatePort(final EnabledDICOMReceiverWithDuplicatePortException exception) { + return new ResponseEntity<>(exception.getMessage(), HttpStatus.CONFLICT); + } + + @ExceptionHandler(NrgServiceException.class) + public ResponseEntity<String> handleNrgServiceException(final NrgServiceException exception) { + return new ResponseEntity<>(exception.getMessage(), HttpStatus.CONFLICT); + } +} diff --git a/src/main/java/org/nrg/xapi/rest/event/EventHandlerApi.java b/src/main/java/org/nrg/xapi/rest/event/EventHandlerApi.java index 9f5bfd5415653d2965fd70907df77fb5a7d560b6..6d9413521067826a3df04a13450746df63167b78 100644 --- a/src/main/java/org/nrg/xapi/rest/event/EventHandlerApi.java +++ b/src/main/java/org/nrg/xapi/rest/event/EventHandlerApi.java @@ -17,12 +17,14 @@ import org.nrg.framework.event.EventClass; import org.nrg.framework.event.Filterable; import org.nrg.framework.utilities.BasicXnatResourceLocator; import org.nrg.xapi.model.event.EventClassInfo; -import org.nrg.xdat.XDAT; +import org.nrg.xapi.model.event.EventHandlerFilterInfo; import org.nrg.xdat.om.XnatProjectdata; import org.nrg.xdat.om.base.auto.AutoXnatProjectdata; +import org.nrg.xdat.rest.AbstractXapiRestController; import org.nrg.xdat.security.XDATUser; import org.nrg.xdat.security.helpers.Permissions; import org.nrg.xdat.security.services.RoleHolder; +import org.nrg.xdat.security.services.UserManagementServiceI; import org.nrg.xft.security.UserI; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,16 +35,15 @@ import org.springframework.core.io.support.PropertiesLoaderUtils; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; -import javax.annotation.PostConstruct; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -54,61 +55,35 @@ import java.util.Properties; */ @Api(description = "The XNAT Event Handler API") @XapiRestController -public class EventHandlerApi { - - /** - * The Constant _log. - */ - private static final Logger _log = LoggerFactory.getLogger(EventHandlerApi.class); - - /** The maximum number of event IDs to return for each event class in each project. */ - private static final int MAX_EVENT_IDS_LIST = 20; - - /** The _role holder. */ - @Autowired - private RoleHolder _roleHolder; - - /** - * The event ids service. - */ +public class EventHandlerApi extends AbstractXapiRestController { @Autowired - private HibernateAutomationEventIdsIdsService eventIdsService; - - /** - * The filters service. - */ - @Autowired - private HibernateAutomationFiltersService filtersService; - - /** - * Inits the this. - */ - @PostConstruct - private void initThis() { - getEventIdsService(); - getFiltersService(); + public EventHandlerApi(final UserManagementServiceI userManagementService, final RoleHolder roleHolder, final HibernateAutomationFiltersService filtersService, final HibernateAutomationEventIdsIdsService eventIdsService) { + super(userManagementService, roleHolder); + _filtersService = filtersService; + _eventIdsService = eventIdsService; } /** * 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) + @ApiOperation(value = "Get list of event classes.", notes = "Returns a list of classes implementing AutomationEventI.", response = EventClassInfo.class, responseContainer = "List") @ApiResponses({@ApiResponse(code = 200, message = "An array of class names"), @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"/projects/{project_id}/eventHandlers/automationEventClasses"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = RequestMethod.GET) @ResponseBody public ResponseEntity<List<EventClassInfo>> automationEventClassesGetByProject(@PathVariable("project_id") String project_id) { - final HttpStatus status = isPermitted(project_id); + final HttpStatus status = canEditProject(project_id); if (status != null) { return new ResponseEntity<>(status); } try { - return new ResponseEntity<>(getEventInfoList(project_id), HttpStatus.OK); + return new ResponseEntity<>(getEventInfoList(project_id), HttpStatus.OK); } catch (Throwable t) { - _log.error("EventHandlerApi exception: " + t.toString()); - return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + _log.error("EventHandlerApi exception: " + t.toString()); + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } @@ -117,37 +92,65 @@ public class EventHandlerApi { * * @return the response entity */ - @ApiOperation(value = "Get list of event classes.", notes = "Returns a list of classes implementing AutomationEventI.", response = List.class) + @ApiOperation(value = "Get list of event classes.", notes = "Returns a list of classes implementing AutomationEventI.", response = EventClassInfo.class, responseContainer = "List") @ApiResponses({@ApiResponse(code = 200, message = "An array of class names"), @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"/eventHandlers/automationEventClasses"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = RequestMethod.GET) @ResponseBody public ResponseEntity<List<EventClassInfo>> automationEventClassesGet() { - final HttpStatus status = isPermitted(null); + final HttpStatus status = isPermitted(); if (status != null) { return new ResponseEntity<>(status); } try { - return new ResponseEntity<>(getEventInfoList(null), HttpStatus.OK); + return new ResponseEntity<>(getEventInfoList(null), HttpStatus.OK); } catch (Throwable t) { - _log.error("EventHandlerApi exception: " + t.toString()); - return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + _log.error("EventHandlerApi exception: " + t.toString()); + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } + /** + * Checks if is permitted. + * + * @param projectId the project ID + * + * @return the http status + */ + // TODO: Migrate this to the abstract superclass. Can't right now because XDAT doesn't know about XnatProjectdata, etc. + protected HttpStatus canEditProject(String projectId) { + final UserI sessionUser = getSessionUser(); + if ((sessionUser instanceof XDATUser)) { + if (projectId != null) { + final XnatProjectdata project = AutoXnatProjectdata.getXnatProjectdatasById(projectId, sessionUser, false); + try { + return Permissions.canEdit(sessionUser, project) ? null : HttpStatus.FORBIDDEN; + } catch (Exception e) { + _log.error("Error checking read status for project", e); + return HttpStatus.INTERNAL_SERVER_ERROR; + } + } else { + return isPermitted() == null ? null : HttpStatus.FORBIDDEN; + } + } + _log.error("Error checking read status for project"); + return HttpStatus.INTERNAL_SERVER_ERROR; + } + /** * 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<AutomationEventIdsIds> eventIdsList = getEventIdsService().getEventIds(project_id, false, MAX_EVENT_IDS_LIST); - final List<AutomationFilters> filtersList = getFiltersService().getAutomationFilters(project_id, false); + final List<EventClassInfo> eventInfoList = Lists.newArrayList(); + final List<AutomationEventIdsIds> eventIdsList = _eventIdsService.getEventIds(project_id, false, MAX_EVENT_IDS_LIST); + final List<AutomationFilters> filtersList = _filtersService.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(); + final EventClassInfo eci = new EventClassInfo(className); + final List<String> eventIds = eci.getEventIds(); + final Map<String, EventHandlerFilterInfo> filterableFields = eci.getFilterableFieldsMap(); if (eci.getIncludeEventIdsFromDatabase()) { for (final AutomationEventIdsIds autoIds : eventIdsList) { if (autoIds.getParentAutomationEventIds().getSrcEventClass().equals(className)) { @@ -163,22 +166,30 @@ public class EventHandlerApi { 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 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 annoInitialValuesObj = AnnotationUtils.getValue(anno, "initialValues"); - final String[] annoInitialValues = (annoInitialValuesObj != null && annoInitialValuesObj instanceof String[]) ? (String[]) annoInitialValuesObj : new String[] {}; + final Object annoDefaultValueObj = AnnotationUtils.getValue(anno, "defaultValue"); + final String annoDefaultValue = (annoDefaultValueObj != null) ? annoDefaultValueObj.toString() : ""; final Object annoFilterRequired = AnnotationUtils.getValue(anno, "filterRequired"); - boolean filterRequired = (annoFilterRequired == null || !(annoFilterRequired instanceof Boolean)) ? false : (boolean) annoFilterRequired; + boolean filterRequired = !(annoFilterRequired == null || !(annoFilterRequired instanceof Boolean)) && (boolean) annoFilterRequired; final Object annoIncludeValuesFromDatabase = AnnotationUtils.getValue(anno, "includeValuesFromDatabase"); - boolean includeValuesFromDatabase = (annoIncludeValuesFromDatabase == null || !(annoIncludeValuesFromDatabase instanceof Boolean)) ? true :(boolean) annoIncludeValuesFromDatabase; + boolean includeValuesFromDatabase = (annoIncludeValuesFromDatabase == null || !(annoIncludeValuesFromDatabase instanceof Boolean)) || (boolean) annoIncludeValuesFromDatabase; if (!filterableFields.containsKey(column)) { - final List<String> newValueList = Lists.newArrayList(); - filterableFields.put(column, newValueList); + EventHandlerFilterInfo filterInfo = new EventHandlerFilterInfo(); + filterInfo.setFilterValues(new ArrayList<String>()); + filterableFields.put(column, filterInfo); } - final List<String> valueList = filterableFields.get(column); + final EventHandlerFilterInfo filterInfo = filterableFields.get(column); + filterInfo.setDefaultValue(annoDefaultValue); + filterInfo.setFilterRequired(filterRequired); + filterInfo.setIncludeValuesFromDatabase(includeValuesFromDatabase); + final List<String> valueList = filterInfo.getFilterValues(); valueList.addAll(Arrays.asList(annoInitialValues)); if (includeValuesFromDatabase) { for (final AutomationFilters autoFilters : filtersList) { @@ -189,23 +200,28 @@ public class EventHandlerApi { } } Collections.sort(valueList); + if (!filterRequired) { - // TODO: Should probably add an EventFilterInfo class and keep the list of filter values there, along with a - // a boolean required value and a default value. - // NOTE: This is handled in JavaScript as non-required filter - valueList.add(0,"_FILTER_NOT_REQUIRED_"); + // TODO: Should probably add an EventFilterInfo class and keep the list of filter values there, along with a + // a boolean required value and a default value. + // NOTE: This is handled in JavaScript as non-required filter + valueList.add(0, "_FILTER_NOT_REQUIRED_"); } } } } catch (SecurityException | ClassNotFoundException e) { for (final AutomationFilters autoFilters : filtersList) { if (!filterableFields.containsKey(autoFilters.getField())) { + EventHandlerFilterInfo filterInfo = new EventHandlerFilterInfo(); final List<String> valueList = Lists.newArrayList(autoFilters.getValues()); + filterInfo.setFilterValues(valueList); + filterInfo.setFilterRequired(false); + filterInfo.setIncludeValuesFromDatabase(true); Collections.sort(valueList); - filterableFields.put(autoFilters.getField(), valueList); + filterableFields.put(autoFilters.getField(), filterInfo); } else { for (final String value : autoFilters.getValues()) { - final List<String> values = filterableFields.get(autoFilters.getField()); + final List<String> values = filterableFields.get(autoFilters.getField()).getFilterValues(); if (!values.contains(value)) { values.add(value); } @@ -223,32 +239,33 @@ public class EventHandlerApi { * Gets the event class list. * * @param eventIdsList the event ids list + * * @return the event class list */ private List<String> getEventClassList(List<AutomationEventIdsIds> eventIdsList) { final List<String> classList = Lists.newArrayList(); try { - for (final Resource resource : BasicXnatResourceLocator.getResources("classpath*:META-INF/xnat/event/*-event.properties")) { - final Properties properties = PropertiesLoaderUtils.loadProperties(resource); - if (!properties.containsKey(EventClass.EVENT_CLASS)) { - continue; - } - final String clssStr = properties.get(EventClass.EVENT_CLASS).toString(); - try { - final Class<?> clazz = Class.forName(clssStr); - if (AutomationEventImplementerI.class.isAssignableFrom(clazz) && !clazz.isInterface() && - !Modifier.isAbstract(clazz.getModifiers())) { - if (!classList.contains(clazz.getName())) { - classList.add(clazz.getName()); - } - } - } catch (ClassNotFoundException cex) { - _log.debug("Could not load class for class name (" + clssStr + ")"); - } - } - } catch (IOException e) { - _log.debug("Could not load event class properties resources (META-INF/xnat/event/*-event.properties)"); - } + for (final Resource resource : BasicXnatResourceLocator.getResources("classpath*:META-INF/xnat/event/*-event.properties")) { + final Properties properties = PropertiesLoaderUtils.loadProperties(resource); + if (!properties.containsKey(EventClass.EVENT_CLASS)) { + continue; + } + final String clssStr = properties.get(EventClass.EVENT_CLASS).toString(); + try { + final Class<?> clazz = Class.forName(clssStr); + if (AutomationEventImplementerI.class.isAssignableFrom(clazz) && !clazz.isInterface() && + !Modifier.isAbstract(clazz.getModifiers())) { + if (!classList.contains(clazz.getName())) { + classList.add(clazz.getName()); + } + } + } catch (ClassNotFoundException cex) { + _log.debug("Could not load class for class name (" + clssStr + ")"); + } + } + } catch (IOException e) { + _log.debug("Could not load event class properties resources (META-INF/xnat/event/*-event.properties)"); + } // 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) { @@ -262,64 +279,15 @@ public class EventHandlerApi { } /** - * Gets the event ids service. - * - * @return the event ids service - */ - private HibernateAutomationEventIdsIdsService getEventIdsService() { - if (eventIdsService == null) { - eventIdsService = XDAT.getContextService().getBean(HibernateAutomationEventIdsIdsService.class); - } - return eventIdsService; - } - - /** - * Gets the filters service. - * - * @return the filters service + * The Constant _log. */ - private HibernateAutomationFiltersService getFiltersService() { - if (filtersService == null) { - filtersService = XDAT.getContextService().getBean(HibernateAutomationFiltersService.class); - } - return filtersService; - } + private static final Logger _log = LoggerFactory.getLogger(EventHandlerApi.class); /** - * Gets the session user. - * - * @return the session user + * The maximum number of event IDs to return for each event class in each project. */ - private UserI getSessionUser() { - final Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); - if ((principal instanceof UserI)) { - return (UserI) principal; - } - return null; - } + private static final int MAX_EVENT_IDS_LIST = 20; - /** - * Checks if is permitted. - * - * @param projectId the project ID - * @return the http status - */ - private HttpStatus isPermitted(String projectId) { - final UserI sessionUser = getSessionUser(); - if ((sessionUser instanceof XDATUser)) { - if (projectId != null) { - final XnatProjectdata project = AutoXnatProjectdata.getXnatProjectdatasById(projectId, sessionUser, false); - try { - return Permissions.canEdit(sessionUser, project) ? null : HttpStatus.FORBIDDEN; - } catch (Exception e) { - _log.error("Error checking read status for project", e); - return HttpStatus.INTERNAL_SERVER_ERROR; - } - } else { - return (_roleHolder.isSiteAdmin(sessionUser)) ? null : HttpStatus.FORBIDDEN; - } - } - _log.error("Error checking read status for project"); - return HttpStatus.INTERNAL_SERVER_ERROR; - } + private final HibernateAutomationEventIdsIdsService _eventIdsService; + private final HibernateAutomationFiltersService _filtersService; } diff --git a/src/main/java/org/nrg/xapi/rest/notifications/NotificationsApi.java b/src/main/java/org/nrg/xapi/rest/notifications/NotificationsApi.java index ebb5c42a9e22c2bbc2a7227fb4d4ed7f5ead0ecc..ec1bcf5f50c176996e789effd21d53cd117f1e78 100644 --- a/src/main/java/org/nrg/xapi/rest/notifications/NotificationsApi.java +++ b/src/main/java/org/nrg/xapi/rest/notifications/NotificationsApi.java @@ -5,17 +5,17 @@ import org.apache.commons.lang3.StringUtils; import org.nrg.framework.annotations.XapiRestController; import org.nrg.framework.exceptions.NrgServiceError; import org.nrg.framework.exceptions.NrgServiceRuntimeException; -import org.nrg.notify.services.NotificationService; +import org.nrg.framework.services.SerializerService; import org.nrg.prefs.exceptions.InvalidPreferenceName; import org.nrg.xapi.exceptions.InitializationException; import org.nrg.xdat.preferences.NotificationsPreferences; import org.nrg.xdat.rest.AbstractXapiRestController; +import org.nrg.xdat.security.services.RoleHolder; +import org.nrg.xdat.security.services.UserManagementServiceI; import org.nrg.xnat.services.XnatAppInfo; import org.nrg.xnat.utils.XnatHttpUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -24,7 +24,10 @@ import org.springframework.web.bind.annotation.*; import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; -import java.util.*; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; @Api(description = "XNAT Notifications management API") @XapiRestController @@ -45,10 +48,23 @@ public class NotificationsApi extends AbstractXapiRestController { + "remove existing properties by setting the property with an empty value. This will " + "modify the existing server configuration. You can completely replace the configuration " + "by calling the POST version of this method."; - @ApiOperation(value = "Returns the full map of site configuration properties.", notes = "Complex objects may be returned as encapsulated JSON strings.", response = String.class, responseContainer = "Map") - @ApiResponses({@ApiResponse(code = 200, message = "Site configuration properties successfully retrieved."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set site configuration properties."), @ApiResponse(code = 500, message = "Unexpected error")}) + + @Inject + public NotificationsApi(final UserManagementServiceI userManagementService, final RoleHolder roleHolder, final NotificationsPreferences notificationsPrefs, final JavaMailSenderImpl javaMailSender, final XnatAppInfo appInfo, final SerializerService serializer) { + super(userManagementService, roleHolder); + _notificationsPrefs = notificationsPrefs; + _javaMailSender = javaMailSender; + _appInfo = appInfo; + _serializer = serializer; + } + + @ApiOperation(value = "Returns the full map of site configuration properties.", notes = "Complex objects may be returned as encapsulated JSON strings.", response = Properties.class) + @ApiResponses({@ApiResponse(code = 200, message = "Site configuration properties successfully retrieved."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to set site configuration properties."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET}) - public ResponseEntity<Map<String, Object>> getAllSiteConfigProperties(final HttpServletRequest request) { + public ResponseEntity<Properties> getAllSiteConfigProperties(@ApiParam(hidden = true) final HttpServletRequest request) throws IOException { final HttpStatus status = isPermitted(); if (status != null) { return new ResponseEntity<>(status); @@ -58,7 +74,7 @@ public class NotificationsApi extends AbstractXapiRestController { _log.debug("User " + username + " requested the site configuration."); } - final Map<String, Object> preferences = _notificationsPrefs.getPreferenceMap(); + final Properties preferences = convertToProperties(_notificationsPrefs.getPreferenceMap()); if (!_appInfo.isInitialized()) { if (_log.isInfoEnabled()) { @@ -67,47 +83,40 @@ public class NotificationsApi extends AbstractXapiRestController { preferences.put("siteUrl", XnatHttpUtils.getServerRoot(request)); } - preferences.put("notifications.emailRecipientErrorMessages",_notificationsPrefs.getEmailRecipientErrorMessages()); - preferences.put("notifications.emailRecipientIssueReports",_notificationsPrefs.getEmailRecipientIssueReports()); - preferences.put("notifications.emailRecipientNewUserAlert",_notificationsPrefs.getEmailRecipientNewUserAlert()); - preferences.put("notifications.emailRecipientUpdate",_notificationsPrefs.getEmailRecipientUpdate()); + preferences.put("notifications.emailRecipientErrorMessages", _notificationsPrefs.getEmailRecipientErrorMessages()); + preferences.put("notifications.emailRecipientIssueReports", _notificationsPrefs.getEmailRecipientIssueReports()); + preferences.put("notifications.emailRecipientNewUserAlert", _notificationsPrefs.getEmailRecipientNewUserAlert()); + preferences.put("notifications.emailRecipientUpdate", _notificationsPrefs.getEmailRecipientUpdate()); return new ResponseEntity<>(preferences, HttpStatus.OK); } @ApiOperation(value = "Sets a map of notifications properties.", notes = "Sets the notifications properties specified in the map.", response = Void.class) - @ApiResponses({@ApiResponse(code = 200, message = "Notifications properties successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set notifications properties."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiResponses({@ApiResponse(code = 200, message = "Notifications properties successfully set."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to set notifications properties."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = "batch", consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE, MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.POST}) - public ResponseEntity<Void> setBatchNotificationsProperties(@ApiParam(value = "The map of notifications properties to be set.", required = true) @RequestBody final Map<String, String> properties) throws InitializationException { + public ResponseEntity<Void> setBatchNotificationsProperties(@ApiParam(value = "The map of notifications properties to be set.", required = true) @RequestBody final Properties properties) throws InitializationException { final HttpStatus status = isPermitted(); if (status != null) { return new ResponseEntity<>(status); } - if (_log.isInfoEnabled()) { - final StringBuilder message = new StringBuilder("User ").append(getSessionUser().getUsername()).append(" is setting the values for the following properties:\n"); - for (final String name : properties.keySet()) { - message.append(" * ").append(name).append(": ").append(properties.get(name)).append("\n"); - } - _log.info(message.toString()); - } + logSetProperties(properties); - for (final String name : properties.keySet()) { + for (final String name : properties.stringPropertyNames()) { try { - if(StringUtils.equals(name, "notifications.emailRecipientErrorMessages")){ - _notificationsPrefs.setEmailRecipientErrorMessages(properties.get(name)); - } - else if(StringUtils.equals(name, "notifications.emailRecipientIssueReports")){ - _notificationsPrefs.setEmailRecipientIssueReports(properties.get(name)); - } - else if(StringUtils.equals(name, "notifications.emailRecipientNewUserAlert")){ - _notificationsPrefs.setEmailRecipientNewUserAlert(properties.get(name)); - } - else if(StringUtils.equals(name, "notifications.emailRecipientUpdate")){ - _notificationsPrefs.setEmailRecipientUpdate(properties.get(name)); - } - else { - _notificationsPrefs.set(properties.get(name), name); + if (StringUtils.equals(name, "notifications.emailRecipientErrorMessages")) { + _notificationsPrefs.setEmailRecipientErrorMessages(properties.getProperty(name)); + } else if (StringUtils.equals(name, "notifications.emailRecipientIssueReports")) { + _notificationsPrefs.setEmailRecipientIssueReports(properties.getProperty(name)); + } else if (StringUtils.equals(name, "notifications.emailRecipientNewUserAlert")) { + _notificationsPrefs.setEmailRecipientNewUserAlert(properties.getProperty(name)); + } else if (StringUtils.equals(name, "notifications.emailRecipientUpdate")) { + _notificationsPrefs.setEmailRecipientUpdate(properties.getProperty(name)); + } else { + _notificationsPrefs.set(properties.getProperty(name), name); } if (_log.isInfoEnabled()) { _log.info("Set property {} to value: {}", name, properties.get(name)); @@ -116,34 +125,27 @@ public class NotificationsApi extends AbstractXapiRestController { _log.error("Got an invalid preference name error for the preference: " + name + ", which is weird because the site configuration is not strict"); } } -// ArcSpecManager initializaton not required for notifications since none of their properties are set in the ArcSpec. -// if (properties.containsKey("initialized") && StringUtils.equals("true", properties.get("initialized"))) { -// initialize(); -// } return new ResponseEntity<>(HttpStatus.OK); } @ApiOperation(value = "Sets a map of notifications properties for mail.", notes = "Sets the notifications properties for mail specified in the map.", response = Void.class) - @ApiResponses({@ApiResponse(code = 200, message = "Notifications properties for mail successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set notifications properties for mail."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiResponses({@ApiResponse(code = 200, message = "Notifications properties for mail successfully set."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to set notifications properties for mail."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = "batchMail", consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE, MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.POST}) - public ResponseEntity<Void> setBatchNotificationsPropertiesForMail(@ApiParam(value = "The map of notifications properties for mail to be set.", required = true) @RequestBody final Map<String, String> properties) throws InitializationException { + public ResponseEntity<Void> setBatchNotificationsPropertiesForMail(@ApiParam(value = "The map of notifications properties for mail to be set.", required = true) @RequestBody final Properties properties) throws InitializationException { final HttpStatus status = isPermitted(); if (status != null) { return new ResponseEntity<>(status); } - if (_log.isInfoEnabled()) { - final StringBuilder message = new StringBuilder("User ").append(getSessionUser().getUsername()).append(" is setting the values for the following properties:\n"); - for (final String name : properties.keySet()) { - message.append(" * ").append(name).append(": ").append(properties.get(name)).append("\n"); - } - _log.info(message.toString()); - } + logSetProperties(properties); - for (final String name : properties.keySet()) { + for (final String name : properties.stringPropertyNames()) { try { - _notificationsPrefs.set(properties.get(name), name); + _notificationsPrefs.set(properties.getProperty(name), name); if (_log.isInfoEnabled()) { _log.info("Set property {} to value: {}", name, properties.get(name)); } @@ -152,13 +154,13 @@ public class NotificationsApi extends AbstractXapiRestController { } } - String host = _notificationsPrefs.getHostname(); - int port = _notificationsPrefs.getPort(); + String host = _notificationsPrefs.getHostname(); + int port = _notificationsPrefs.getPort(); String protocol = _notificationsPrefs.getProtocol(); String username = _notificationsPrefs.getUsername(); String password = _notificationsPrefs.getPassword(); - logConfigurationSubmit(host,port,protocol,username,password, properties); + logConfigurationSubmit(host, port, protocol, username, password, properties); setHost(host, false); setPort(port); @@ -166,25 +168,20 @@ public class NotificationsApi extends AbstractXapiRestController { setUsername(username); setPassword(password); - final Properties javaMailProperties = new Properties(); - if (properties != null) { - for (final String property : properties.keySet()) { - final String value = properties.get(property); - if (StringUtils.isNotBlank(value)) { - javaMailProperties.setProperty(property, value); - } - } - } - _javaMailSender.setJavaMailProperties(javaMailProperties); + _javaMailSender.setJavaMailProperties(getSubmittedProperties(properties)); setSmtp(); return new ResponseEntity<>(HttpStatus.OK); } - - @ApiOperation(value = "Returns the full SMTP server configuration.", notes = "Returns the configuration as a map of the standard Java mail sender properties–host, port, protocol, username, and password–along with any extended properties required for the configuration, e.g. configuring SSL- or TLS-secured SMTP services.", response = Properties.class) - @ApiResponses({@ApiResponse(code = 200, message = "SMTP service configuration properties successfully retrieved."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set site configuration properties."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Returns the full SMTP server configuration.", + notes = "Returns the configuration as a map of the standard Java mail sender properties–host, port, protocol, username, and password–along with any extended properties required for the configuration, e.g. configuring SSL- or TLS-secured SMTP services.", + response = Properties.class) + @ApiResponses({@ApiResponse(code = 200, message = "SMTP service configuration properties successfully retrieved."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to set site configuration properties."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = "smtp", produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET}) public ResponseEntity<Properties> getSmtpServerProperties() { final HttpStatus status = isPermitted(); @@ -218,15 +215,20 @@ public class NotificationsApi extends AbstractXapiRestController { return new ResponseEntity<>(properties, HttpStatus.OK); } - @ApiOperation(value = "Sets the mail service properties. This return the SMTP server configuration as it exists after being set.", notes = POST_PROPERTIES_NOTES, response = Properties.class) - @ApiResponses({@ApiResponse(code = 200, message = "Mail service properties successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set the mail service properties."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Sets the mail service properties. This return the SMTP server configuration as it exists after being set.", + notes = POST_PROPERTIES_NOTES, + response = Properties.class) + @ApiResponses({@ApiResponse(code = 200, message = "Mail service properties successfully set."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to set the mail service properties."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"smtp"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.POST}) public ResponseEntity<Properties> setAllMailProperties(@ApiParam("The value to set for the email host.") @RequestParam(value = "host", required = false, defaultValue = NOT_SET) final String host, @ApiParam("The value to set for the email port.") @RequestParam(value = "port", required = false, defaultValue = "-1") final int port, @ApiParam("The value to set for the email username.") @RequestParam(value = "username", required = false, defaultValue = NOT_SET) final String username, @ApiParam("The value to set for the email password.") @RequestParam(value = "password", required = false, defaultValue = NOT_SET) final String password, @ApiParam("The value to set for the email protocol.") @RequestParam(value = "protocol", required = false, defaultValue = NOT_SET) final String protocol, - @ApiParam(value = "Values to set for extra mail properties. An empty value indicates that an existing property should be removed.") @RequestParam final Map<String, String> properties) { + @ApiParam("Values to set for extra mail properties. An empty value indicates that an existing property should be removed.") @RequestParam final Properties properties) { final HttpStatus status = isPermitted(); if (status != null) { return new ResponseEntity<>(status); @@ -241,41 +243,27 @@ public class NotificationsApi extends AbstractXapiRestController { setUsername(username); setPassword(password); - // Set all of the submitted properties. - final Properties javaMailProperties = new Properties(); - if (properties != null) { - for (final String property : properties.keySet()) { - final String value = properties.get(property); - if (StringUtils.isNotBlank(value)) { - javaMailProperties.setProperty(property, value); - } - } - } - - // Find any existing properties that weren't submitted... - final Properties existing = _javaMailSender.getJavaMailProperties(); - for (final String property : existing.stringPropertyNames()) { - if (!javaMailProperties.containsKey(property)) { - // Set the value to "", this will remove the property. - javaMailProperties.setProperty(property, ""); - } - } - _javaMailSender.setJavaMailProperties(javaMailProperties); + _javaMailSender.setJavaMailProperties(getSubmittedProperties(properties)); setSmtp(); return getSmtpServerProperties(); } - @ApiOperation(value = "Sets the submitted mail service properties. This returns the SMTP server configuration as it exists after being set.", notes = PUT_PROPERTIES_NOTES, response = Properties.class) - @ApiResponses({@ApiResponse(code = 200, message = "Mail service properties successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set the mail service properties."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Sets the submitted mail service properties. This returns the SMTP server configuration as it exists after being set.", + notes = PUT_PROPERTIES_NOTES, + response = Properties.class) + @ApiResponses({@ApiResponse(code = 200, message = "Mail service properties successfully set."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to set the mail service properties."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"smtp"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT}) public ResponseEntity<Properties> setSubmittedMailProperties(@ApiParam("The value to set for the email host.") @RequestParam(value = "host", required = false, defaultValue = NOT_SET) final String host, @ApiParam("The value to set for the email port.") @RequestParam(value = "port", required = false, defaultValue = "-1") final int port, @ApiParam("The value to set for the email username.") @RequestParam(value = "username", required = false, defaultValue = NOT_SET) final String username, @ApiParam("The value to set for the email password.") @RequestParam(value = "password", required = false, defaultValue = NOT_SET) final String password, @ApiParam("The value to set for the email protocol.") @RequestParam(value = "protocol", required = false, defaultValue = NOT_SET) final String protocol, - @ApiParam(value = "Values to set for extra mail properties. An empty value indicates that an existing property should be removed.") @RequestParam final Map<String, String> properties) { + @ApiParam("Values to set for extra mail properties. An empty value indicates that an existing property should be removed.") @RequestParam final Properties properties) { final HttpStatus status = isPermitted(); if (status != null) { return new ResponseEntity<>(status); @@ -289,8 +277,8 @@ public class NotificationsApi extends AbstractXapiRestController { setUsername(username); setPassword(password); if (properties != null) { - for (final String property : properties.keySet()) { - _javaMailSender.getJavaMailProperties().setProperty(property, properties.get(property)); + for (final String property : properties.stringPropertyNames()) { + _javaMailSender.getJavaMailProperties().setProperty(property, properties.getProperty(property)); } } @@ -299,8 +287,13 @@ public class NotificationsApi extends AbstractXapiRestController { return getSmtpServerProperties(); } - @ApiOperation(value = "Sets the mail service host.", notes = "Sets the mail service host.", response = Properties.class) - @ApiResponses({@ApiResponse(code = 200, message = "Mail service host successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set the mail service host."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Sets the mail service host.", + notes = "Sets the mail service host.", + response = Properties.class) + @ApiResponses({@ApiResponse(code = 200, message = "Mail service host successfully set."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to set the mail service host."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"smtp/host/{host}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT}) public ResponseEntity<Properties> setHostProperty(@ApiParam(value = "The value to set for the email host.", required = true) @PathVariable final String host) { final HttpStatus status = isPermitted(); @@ -315,8 +308,13 @@ public class NotificationsApi extends AbstractXapiRestController { return getSmtpServerProperties(); } - @ApiOperation(value = "Sets the mail service port.", notes = "Sets the mail service port.", response = Properties.class) - @ApiResponses({@ApiResponse(code = 200, message = "Mail service port successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set the mail service port."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Sets the mail service port.", + notes = "Sets the mail service port.", + response = Properties.class) + @ApiResponses({@ApiResponse(code = 200, message = "Mail service port successfully set."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to set the mail service port."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"smtp/port/{port}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT}) public ResponseEntity<Properties> setPortProperty(@ApiParam(value = "The value to set for the email port.", required = true) @PathVariable final int port) { final HttpStatus status = isPermitted(); @@ -331,8 +329,13 @@ public class NotificationsApi extends AbstractXapiRestController { return getSmtpServerProperties(); } - @ApiOperation(value = "Sets the mail service protocol.", notes = "Sets the mail service protocol.", response = Properties.class) - @ApiResponses({@ApiResponse(code = 200, message = "Mail service protocol successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set the mail service protocol."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Sets the mail service protocol.", + notes = "Sets the mail service protocol.", + response = Properties.class) + @ApiResponses({@ApiResponse(code = 200, message = "Mail service protocol successfully set."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to set the mail service protocol."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"smtp/protocol/{protocol}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT}) public ResponseEntity<Properties> setProtocolProperty(@ApiParam(value = "The value to set for the email protocol.", required = true) @PathVariable final String protocol) { final HttpStatus status = isPermitted(); @@ -346,8 +349,13 @@ public class NotificationsApi extends AbstractXapiRestController { return getSmtpServerProperties(); } - @ApiOperation(value = "Sets the mail service username.", notes = "Sets the mail service username.", response = Properties.class) - @ApiResponses({@ApiResponse(code = 200, message = "Mail service username successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set the mail service username."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Sets the mail service username.", + notes = "Sets the mail service username.", + response = Properties.class) + @ApiResponses({@ApiResponse(code = 200, message = "Mail service username successfully set."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to set the mail service username."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"smtp/username/{username}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT}) public ResponseEntity<Properties> setUsernameProperty(@ApiParam(value = "The value to set for the email username.", required = true) @PathVariable final String username) { final HttpStatus status = isPermitted(); @@ -361,8 +369,13 @@ public class NotificationsApi extends AbstractXapiRestController { return getSmtpServerProperties(); } - @ApiOperation(value = "Sets the mail service password.", notes = "Sets the mail service password.", response = Properties.class) - @ApiResponses({@ApiResponse(code = 200, message = "Mail service password successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set the mail service password."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Sets the mail service password.", + notes = "Sets the mail service password.", + response = Properties.class) + @ApiResponses({@ApiResponse(code = 200, message = "Mail service password successfully set."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to set the mail service password."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"smtp/password/{password}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT}) public ResponseEntity<Properties> setPasswordProperty(@ApiParam(value = "The value to set for the email password.", required = true) @PathVariable final String password) { final HttpStatus status = isPermitted(); @@ -376,8 +389,13 @@ public class NotificationsApi extends AbstractXapiRestController { return getSmtpServerProperties(); } - @ApiOperation(value = "Sets a Java mail property with the submitted name and value.", notes = "Setting a property to an empty value will remove the property.", response = Properties.class) - @ApiResponses({@ApiResponse(code = 200, message = "Mail service password successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set the mail service password."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Sets a Java mail property with the submitted name and value.", + notes = "Setting a property to an empty value will remove the property.", + response = Properties.class) + @ApiResponses({@ApiResponse(code = 200, message = "Mail service password successfully set."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to set the mail service password."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"smtp/property/{property}/{value}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT}) public ResponseEntity<Properties> setExtendedProperty(@ApiParam(value = "The value to set for the email password.", required = true) @PathVariable final String property, @ApiParam(value = "The value to set for the email password.", required = true) @PathVariable final String value) { @@ -392,203 +410,375 @@ public class NotificationsApi extends AbstractXapiRestController { return getSmtpServerProperties(); } - @ApiOperation(value = "Sets the email message for contacting help.", notes = "Sets the email message that people should receive when contacting help.", response = String.class) - @ApiResponses({@ApiResponse(code = 200, message = "Help email message successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set the help email message."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Sets the email message for contacting help.", + notes = "Sets the email message that people should receive when contacting help.", + response = Void.class) + @ApiResponses({@ApiResponse(code = 200, message = "Help email message successfully set."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to set the help email message."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"messages/help"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.POST}) public ResponseEntity<Void> setHelpContactInfo(@ApiParam(value = "The email message for contacting help.", required = true) @RequestParam final String message) { _notificationsPrefs.setHelpContactInfo(message); return new ResponseEntity<>(HttpStatus.OK); } - @ApiOperation(value = "Sets the email message for user registration.", notes = "Sets the email message that people should receive when they register. Link for email validation is auto-populated.", response = String.class) - @ApiResponses({@ApiResponse(code = 200, message = "User registration email message successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set the user registration email message."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Sets the email message for user registration.", + notes = "Sets the email message that people should receive when they register. Link for email validation is auto-populated.", + response = Properties.class) + @ApiResponses({@ApiResponse(code = 200, message = "User registration email message successfully set."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to set the user registration email message."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"messages/registration"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.POST}) public ResponseEntity<Properties> setEmailMessageUserRegistration(@ApiParam(value = "The email message for user registration.", required = true) @RequestParam final String message) { _notificationsPrefs.setEmailMessageUserRegistration(message); return new ResponseEntity<>(HttpStatus.OK); } - @ApiOperation(value = "Sets the email message for forgot username.", notes = "Sets the email message that people should receive when they click that they forgot their username.", response = String.class) - @ApiResponses({@ApiResponse(code = 200, message = "Forgot username email message successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set the forgot username email message."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Sets the email message for forgot username.", + notes = "Sets the email message that people should receive when they click that they forgot their username.", + response = Properties.class) + @ApiResponses({@ApiResponse(code = 200, message = "Forgot username email message successfully set."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to set the forgot username email message."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"messages/forgotusername"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.POST}) public ResponseEntity<Properties> setEmailMessageForgotUsernameRequest(@ApiParam(value = "The email message for forgot username.", required = true) @RequestParam final String message) { _notificationsPrefs.setEmailMessageForgotUsernameRequest(message); return new ResponseEntity<>(HttpStatus.OK); } - @ApiOperation(value = "Sets the email message for password reset.", notes = "Sets the email message that people should receive when they click to reset their password. Link for password reset is auto-populated.", response = String.class) - @ApiResponses({@ApiResponse(code = 200, message = "Password reset message successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set the password reset message."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Sets the email message for password reset.", + notes = "Sets the email message that people should receive when they click to reset their password. Link for password reset is auto-populated.", + response = Properties.class) + @ApiResponses({@ApiResponse(code = 200, message = "Password reset message successfully set."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to set the password reset message."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"messages/passwordreset"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.POST}) public ResponseEntity<Properties> setEmailMessageForgotPasswordReset(@ApiParam(value = "The email message for password reset.", required = true) @RequestParam final String message) { _notificationsPrefs.setEmailMessageForgotPasswordReset(message); return new ResponseEntity<>(HttpStatus.OK); } - @ApiOperation(value = "Returns the email message for contacting help.", notes = "This returns the email message that people should receive when contacting help.", response = String.class) - @ApiResponses({@ApiResponse(code = 200, message = "Email message for contacting help successfully returned."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to get email message for contacting help."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Returns the email message for contacting help.", + notes = "This returns the email message that people should receive when contacting help.", + response = String.class) + @ApiResponses({@ApiResponse(code = 200, message = "Email message for contacting help successfully returned."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to get email message for contacting help."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"messages/help"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET}) public ResponseEntity<String> getHelpContactInfo() { return new ResponseEntity<>(_notificationsPrefs.getHelpContactInfo(), HttpStatus.OK); } - @ApiOperation(value = "Returns the email message for user registration.", notes = "This returns the email message that people should receive when they register. Link for email validation is auto-populated.", response = String.class) - @ApiResponses({@ApiResponse(code = 200, message = "Email message for user registration successfully returned."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to get email message for user registration."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Returns the email message for user registration.", + notes = "This returns the email message that people should receive when they register. Link for email validation is auto-populated.", + response = String.class) + @ApiResponses({@ApiResponse(code = 200, message = "Email message for user registration successfully returned."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to get email message for user registration."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"messages/registration"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET}) public ResponseEntity<String> getEmailMessageUserRegistration() { return new ResponseEntity<>(_notificationsPrefs.getEmailMessageUserRegistration(), HttpStatus.OK); } - @ApiOperation(value = "Returns the email message for forgot username.", notes = "This returns the email message that people should receive when they click that they forgot their username.", response = String.class) - @ApiResponses({@ApiResponse(code = 200, message = "Email message for forgot username successfully returned."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to get email message for forgot username."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Returns the email message for forgot username.", + notes = "This returns the email message that people should receive when they click that they forgot their username.", + response = String.class) + @ApiResponses({@ApiResponse(code = 200, message = "Email message for forgot username successfully returned."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to get email message for forgot username."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"messages/forgotusername"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET}) public ResponseEntity<String> getEmailMessageForgotUsernameRequest() { return new ResponseEntity<>(_notificationsPrefs.getEmailMessageForgotUsernameRequest(), HttpStatus.OK); } - @ApiOperation(value = "Returns the email message for password reset.", notes = "This returns the email message that people should receive when they click to reset their password. Link for password reset is auto-populated.", response = String.class) - @ApiResponses({@ApiResponse(code = 200, message = "Email message for password reset successfully returned."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to get email message for password reset."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Returns the email message for password reset.", + notes = "This returns the email message that people should receive when they click to reset their password. Link for password reset is auto-populated.", + response = String.class) + @ApiResponses({@ApiResponse(code = 200, message = "Email message for password reset successfully returned."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to get email message for password reset."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"messages/passwordreset"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET}) public ResponseEntity<String> getEmailMessageForgotPasswordReset() { return new ResponseEntity<>(_notificationsPrefs.getEmailMessageForgotPasswordReset(), HttpStatus.OK); } - @ApiOperation(value = "Sets whether admins should be notified of user registration.", notes = "Sets whether admins should be notified of user registration.", response = String.class) - @ApiResponses({@ApiResponse(code = 200, message = "Whether admins should be notified of user registration successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set whether admins should be notified of user registration."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Sets whether admins should be notified of user registration.", + notes = "Sets whether admins should be notified of user registration.", + response = Void.class) + @ApiResponses({@ApiResponse(code = 200, message = "Whether admins should be notified of user registration successfully set."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to set whether admins should be notified of user registration."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"notify/registration"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.POST}) public ResponseEntity<Void> setNotifyAdminUserRegistration(@ApiParam(value = "Whether admins should be notified of user registration successfully set.", required = true) @RequestParam final boolean notify) { _notificationsPrefs.setNotifyAdminUserRegistration(notify); return new ResponseEntity<>(HttpStatus.OK); } - @ApiOperation(value = "Sets whether admins should be notified of pipeline processing submit.", notes = "Sets whether admins should be notified of pipeline processing submit.", response = String.class) - @ApiResponses({@ApiResponse(code = 200, message = "Whether admins should be notified of pipeline processing submit successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set whether admins should be notified of pipeline processing submit."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Sets whether admins should be notified of pipeline processing submit.", + notes = "Sets whether admins should be notified of pipeline processing submit.", + response = Properties.class) + @ApiResponses({@ApiResponse(code = 200, message = "Whether admins should be notified of pipeline processing submit successfully set."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to set whether admins should be notified of pipeline processing submit."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"notify/pipeline"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.POST}) public ResponseEntity<Properties> setNotifyAdminPipelineEmails(@ApiParam(value = "Whether admins should be notified of pipeline processing submit successfully set.", required = true) @RequestParam final boolean notify) { _notificationsPrefs.setNotifyAdminPipelineEmails(notify); return new ResponseEntity<>(HttpStatus.OK); } - @ApiOperation(value = "Sets whether admins should be notified of project access requests.", notes = "Sets whether admins should be notified of project access requests.", response = String.class) - @ApiResponses({@ApiResponse(code = 200, message = "Whether admins should be notified of project access requests successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set whether admins should be notified of project access requests."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Sets whether admins should be notified of project access requests.", + notes = "Sets whether admins should be notified of project access requests.", + response = Properties.class) + @ApiResponses({@ApiResponse(code = 200, message = "Whether admins should be notified of project access requests successfully set."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to set whether admins should be notified of project access requests."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"notify/par"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.POST}) public ResponseEntity<Properties> setNotifyAdminProjectAccessRequest(@ApiParam(value = "Whether admins should be notified of project access requests successfully set.", required = true) @RequestParam final boolean notify) { _notificationsPrefs.setNotifyAdminProjectAccessRequest(notify); return new ResponseEntity<>(HttpStatus.OK); } - @ApiOperation(value = "Sets whether admins should be notified of session transfer.", notes = "Sets whether admins should be notified of session transfer by user.", response = String.class) - @ApiResponses({@ApiResponse(code = 200, message = "Whether admins should be notified of session transfer successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set whether admins should be notified of session transfer."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Sets whether admins should be notified of session transfer.", + notes = "Sets whether admins should be notified of session transfer by user.", + response = Properties.class) + @ApiResponses({@ApiResponse(code = 200, message = "Whether admins should be notified of session transfer successfully set."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to set whether admins should be notified of session transfer."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"notify/transfer"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.POST}) public ResponseEntity<Properties> setNotifyAdminSessionTransfer(@ApiParam(value = "Whether admins should be notified of session transfer successfully set.", required = true) @RequestParam final boolean notify) { _notificationsPrefs.setNotifyAdminSessionTransfer(notify); return new ResponseEntity<>(HttpStatus.OK); } - @ApiOperation(value = "Returns whether admins should be notified of user registration.", notes = "This returns whether admins should be notified of user registration.", response = Boolean.class) - @ApiResponses({@ApiResponse(code = 200, message = "Email message for contacting help successfully returned."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to get email message for contacting help."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Returns whether admins should be notified of user registration.", + notes = "This returns whether admins should be notified of user registration.", + response = Boolean.class) + @ApiResponses({@ApiResponse(code = 200, message = "Email message for contacting help successfully returned."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to get email message for contacting help."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"notify/registration"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET}) public ResponseEntity<Boolean> getNotifyAdminUserRegistration() { return new ResponseEntity<>(_notificationsPrefs.getNotifyAdminUserRegistration(), HttpStatus.OK); } - @ApiOperation(value = "Returns whether admins should be notified of pipeline processing submit.", notes = "This returns whether admins should be notified of pipeline processing submit.", response = Boolean.class) - @ApiResponses({@ApiResponse(code = 200, message = "Email message for user registration successfully returned."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to get email message for user registration."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Returns whether admins should be notified of pipeline processing submit.", + notes = "This returns whether admins should be notified of pipeline processing submit.", + response = Boolean.class) + @ApiResponses({@ApiResponse(code = 200, message = "Email message for user registration successfully returned."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to get email message for user registration."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"notify/pipeline"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET}) public ResponseEntity<Boolean> getNotifyAdminPipelineEmails() { return new ResponseEntity<>(_notificationsPrefs.getNotifyAdminPipelineEmails(), HttpStatus.OK); } - @ApiOperation(value = "Returns whether admins should be notified of project access requests.", notes = "This returns whether admins should be notified of project access requests.", response = Boolean.class) - @ApiResponses({@ApiResponse(code = 200, message = "Email message for forgot username successfully returned."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to get email message for forgot username."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Returns whether admins should be notified of project access requests.", + notes = "This returns whether admins should be notified of project access requests.", + response = Boolean.class) + @ApiResponses({@ApiResponse(code = 200, message = "Email message for forgot username successfully returned."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to get email message for forgot username."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"notify/par"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET}) public ResponseEntity<Boolean> getNotifyAdminProjectAccessRequest() { return new ResponseEntity<>(_notificationsPrefs.getNotifyAdminProjectAccessRequest(), HttpStatus.OK); } - @ApiOperation(value = "Returns whether admins should be notified of session transfer.", notes = "This returns whether admins should be notified of session transfer.", response = Boolean.class) - @ApiResponses({@ApiResponse(code = 200, message = "Email message for password reset successfully returned."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to get email message for password reset."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Returns whether admins should be notified of session transfer.", + notes = "This returns whether admins should be notified of session transfer.", + response = Boolean.class) + @ApiResponses({@ApiResponse(code = 200, message = "Email message for password reset successfully returned."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to get email message for password reset."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"notify/transfer"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET}) public ResponseEntity<Boolean> getNotifyAdminSessionTransfer() { return new ResponseEntity<>(_notificationsPrefs.getNotifyAdminSessionTransfer(), HttpStatus.OK); } - @ApiOperation(value = "Sets whether non-users should be able to subscribe to notifications.", notes = "Sets whether non-users should be able to subscribe to notifications.", response = String.class) - @ApiResponses({@ApiResponse(code = 200, message = "Whether non-users should be able to subscribe to notifications."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set whether non-users should be able to subscribe to notifications."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Sets whether non-users should be able to subscribe to notifications.", + notes = "Sets whether non-users should be able to subscribe to notifications.", + response = Properties.class) + @ApiResponses({@ApiResponse(code = 200, message = "Whether non-users should be able to subscribe to notifications."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to set whether non-users should be able to subscribe to notifications."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"allow/nonusersubscribers/{setting}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.POST}) public ResponseEntity<Properties> setEmailAllowNonuserSubscribers(@ApiParam(value = "Whether non-users should be able to subscribe to notifications.", required = true) @PathVariable final boolean setting) { _notificationsPrefs.setEmailAllowNonuserSubscribers(setting); return new ResponseEntity<>(HttpStatus.OK); } - @ApiOperation(value = "Returns whether non-users should be able to subscribe to notifications.", notes = "This returns whether non-users should be able to subscribe to notifications.", response = Boolean.class) - @ApiResponses({@ApiResponse(code = 200, message = "Whether non-users should be able to subscribe to notifications successfully returned."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to get whether non-users should be able to subscribe to notifications."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Returns whether non-users should be able to subscribe to notifications.", + notes = "This returns whether non-users should be able to subscribe to notifications.", + response = Boolean.class) + @ApiResponses({@ApiResponse(code = 200, message = "Whether non-users should be able to subscribe to notifications successfully returned."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to get whether non-users should be able to subscribe to notifications."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"allow/nonusersubscribers"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET}) public ResponseEntity<Boolean> getEmailAllowNonuserSubscribers() { return new ResponseEntity<>(_notificationsPrefs.getEmailAllowNonuserSubscribers(), HttpStatus.OK); } - @ApiOperation(value = "Sets the email addresses for error notifications.", notes = "Sets the email addresses that should be subscribed to error notifications.", response = Properties.class) - @ApiResponses({@ApiResponse(code = 200, message = "Error subscribers successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set the error subscribers."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Sets the email addresses for error notifications.", + notes = "Sets the email addresses that should be subscribed to error notifications.", + response = Void.class) + @ApiResponses({@ApiResponse(code = 200, message = "Error subscribers successfully set."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to set the error subscribers."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"subscribers/error"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.POST}) public ResponseEntity<Void> setErrorSubscribers(@ApiParam(value = "The values to set for email addresses for error notifications.", required = true) @RequestParam final String subscribers) { _notificationsPrefs.setEmailRecipientErrorMessages(subscribers); return new ResponseEntity<>(HttpStatus.OK); } - @ApiOperation(value = "Sets the email addresses for issue notifications.", notes = "Sets the email addresses that should be subscribed to issue notifications.", response = Properties.class) - @ApiResponses({@ApiResponse(code = 200, message = "Issue subscribers successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set the issue subscribers."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Sets the email addresses for issue notifications.", + notes = "Sets the email addresses that should be subscribed to issue notifications.", + response = Properties.class) + @ApiResponses({@ApiResponse(code = 200, message = "Issue subscribers successfully set."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to set the issue subscribers."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"subscribers/issue"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.POST}) public ResponseEntity<Properties> setIssueSubscribers(@ApiParam(value = "The values to set for email addresses for issue notifications.", required = true) @RequestParam final String subscribers) { _notificationsPrefs.setEmailRecipientIssueReports(subscribers); return new ResponseEntity<>(HttpStatus.OK); } - @ApiOperation(value = "Sets the email addresses for new user notifications.", notes = "Sets the email addresses that should be subscribed to new user notifications.", response = Properties.class) - @ApiResponses({@ApiResponse(code = 200, message = "New user subscribers successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set the new user subscribers."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Sets the email addresses for new user notifications.", + notes = "Sets the email addresses that should be subscribed to new user notifications.", + response = Properties.class) + @ApiResponses({@ApiResponse(code = 200, message = "New user subscribers successfully set."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to set the new user subscribers."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"subscribers/newuser"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.POST}) public ResponseEntity<Properties> setNewUserSubscribers(@ApiParam(value = "The values to set for email addresses for new user notifications.", required = true) @RequestParam final String subscribers) { _notificationsPrefs.setEmailRecipientNewUserAlert(subscribers); return new ResponseEntity<>(HttpStatus.OK); } - @ApiOperation(value = "Sets the email addresses for update notifications.", notes = "Sets the email addresses that should be subscribed to update notifications.", response = Properties.class) - @ApiResponses({@ApiResponse(code = 200, message = "Update subscribers successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set the update subscribers."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Sets the email addresses for update notifications.", + notes = "Sets the email addresses that should be subscribed to update notifications.", + response = Properties.class) + @ApiResponses({@ApiResponse(code = 200, message = "Update subscribers successfully set."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to set the update subscribers."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"subscribers/update"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.POST}) public ResponseEntity<Properties> setUpdateSubscribers(@ApiParam(value = "The values to set for email addresses for update notifications.", required = true) @RequestParam final String subscribers) { _notificationsPrefs.setEmailRecipientUpdate(subscribers); return new ResponseEntity<>(HttpStatus.OK); } - @ApiOperation(value = "Returns list of email addresses subscribed to error notifications.", notes = "This returns a list of all the email addresses that are subscribed to receive error notifications.", response = String.class) - @ApiResponses({@ApiResponse(code = 200, message = "Error notification subscribers successfully returned."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to get subscribers for email notifications."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Returns list of email addresses subscribed to error notifications.", + notes = "This returns a list of all the email addresses that are subscribed to receive error notifications.", + response = String.class) + @ApiResponses({@ApiResponse(code = 200, message = "Error notification subscribers successfully returned."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to get subscribers for email notifications."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"subscribers/error"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET}) public ResponseEntity<String> getErrorSubscribers() { return new ResponseEntity<>(_notificationsPrefs.getEmailRecipientErrorMessages(), HttpStatus.OK); } - @ApiOperation(value = "Returns list of email addresses subscribed to issue notifications.", notes = "This returns a list of all the email addresses that are subscribed to receive issue notifications.", response = String.class) - @ApiResponses({@ApiResponse(code = 200, message = "Issue notification subscribers successfully returned."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to get subscribers for email notifications."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Returns list of email addresses subscribed to issue notifications.", + notes = "This returns a list of all the email addresses that are subscribed to receive issue notifications.", + response = String.class) + @ApiResponses({@ApiResponse(code = 200, message = "Issue notification subscribers successfully returned."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to get subscribers for email notifications."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"subscribers/issue"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET}) public ResponseEntity<String> getIssueSubscribers() { return new ResponseEntity<>(_notificationsPrefs.getEmailRecipientIssueReports(), HttpStatus.OK); } - @ApiOperation(value = "Returns list of email addresses subscribed to new user notifications.", notes = "This returns a list of all the email addresses that are subscribed to receive new user notifications.", response = String.class) - @ApiResponses({@ApiResponse(code = 200, message = "New user notification subscribers successfully returned."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to get subscribers for email notifications."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Returns list of email addresses subscribed to new user notifications.", + notes = "This returns a list of all the email addresses that are subscribed to receive new user notifications.", + response = String.class) + @ApiResponses({@ApiResponse(code = 200, message = "New user notification subscribers successfully returned."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to get subscribers for email notifications."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"subscribers/newuser"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET}) public ResponseEntity<String> getNewUserSubscribers() { return new ResponseEntity<>(_notificationsPrefs.getEmailRecipientNewUserAlert(), HttpStatus.OK); } - @ApiOperation(value = "Returns list of email addresses subscribed to update notifications.", notes = "This returns a list of all the email addresses that are subscribed to receive update notifications.", response = String.class) - @ApiResponses({@ApiResponse(code = 200, message = "Update notification subscribers successfully returned."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to get subscribers for email notifications."), @ApiResponse(code = 500, message = "Unexpected error")}) + @ApiOperation(value = "Returns list of email addresses subscribed to update notifications.", + notes = "This returns a list of all the email addresses that are subscribed to receive update notifications.", + response = String.class) + @ApiResponses({@ApiResponse(code = 200, message = "Update notification subscribers successfully returned."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to get subscribers for email notifications."), + @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"subscribers/update"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET}) public ResponseEntity<String> getUpdateSubscribers() { return new ResponseEntity<>(_notificationsPrefs.getEmailRecipientUpdate(), HttpStatus.OK); } + protected Properties getSubmittedProperties(final Properties properties) { + // Set all of the submitted properties. + final Properties javaMailProperties = new Properties(); + if (properties != null) { + for (final String property : properties.stringPropertyNames()) { + final String value = properties.getProperty(property); + if (StringUtils.isNotBlank(value)) { + javaMailProperties.setProperty(property, value); + } + } + } + + // Find any existing properties that weren't submitted... + final Properties existing = _javaMailSender.getJavaMailProperties(); + for (final String property : existing.stringPropertyNames()) { + if (!javaMailProperties.containsKey(property)) { + // Set the value to "", this will remove the property. + javaMailProperties.setProperty(property, ""); + } + } + return javaMailProperties; + } + + private Properties convertToProperties(final Map<String, Object> preferenceMap) throws IOException { + final Properties properties = new Properties(); + for (final String key : preferenceMap.keySet()) { + final Object object = preferenceMap.get(key); + String tempVal = ""; + if(object!=null){ + if(String.class.isAssignableFrom(object.getClass())){ + tempVal = (String)object; + } + else{ + tempVal = _serializer.toJson(object); + } + } + final String value = tempVal; + properties.setProperty(key, value); + } + return properties; + } + private void setSmtp() { - Map<String, String> smtp = new HashMap<>(); + final Map<String, String> smtp = new HashMap<>(); smtp.put("host", StringUtils.defaultIfBlank(_javaMailSender.getHost(), "localhost")); smtp.put("port", Integer.toString(_javaMailSender.getPort())); if (StringUtils.isNotBlank(_javaMailSender.getUsername())) { @@ -609,7 +799,7 @@ public class NotificationsApi extends AbstractXapiRestController { _notificationsPrefs.setSmtpServer(smtp); } - private void cleanProperties(final Map<String, String> properties) { + private void cleanProperties(final Properties properties) { if (properties.containsKey("host")) { properties.remove("host"); } @@ -669,7 +859,7 @@ public class NotificationsApi extends AbstractXapiRestController { } } - private void logConfigurationSubmit(final String host, final int port, final String username, final String password, final String protocol, final Map<String, String> properties) { + private void logConfigurationSubmit(final String host, final int port, final String username, final String password, final String protocol, final Properties properties) { if (_log.isInfoEnabled()) { final StringBuilder message = new StringBuilder("User "); message.append(getSessionUser().getLogin()).append(" setting mail properties to:\n"); @@ -679,7 +869,7 @@ public class NotificationsApi extends AbstractXapiRestController { message.append(" * Username: ").append(StringUtils.equals(NOT_SET, username) ? "No value submitted..." : username).append("\n"); message.append(" * Password: ").append(StringUtils.equals(NOT_SET, password) ? "No value submitted..." : "********").append("\n"); if (properties != null && properties.size() > 0) { - for (final String property : properties.keySet()) { + for (final String property : properties.stringPropertyNames()) { message.append(" * ").append(property).append(": ").append(properties.get(property)).append("\n"); } } @@ -690,17 +880,8 @@ public class NotificationsApi extends AbstractXapiRestController { private static final Logger _log = LoggerFactory.getLogger(NotificationsApi.class); private static final String NOT_SET = "NotSet"; - @Autowired - @Lazy - private NotificationsPreferences _notificationsPrefs; - - @Inject - private JavaMailSenderImpl _javaMailSender; - - @Inject - private NotificationService _notificationService; - - @Autowired - @Lazy - private XnatAppInfo _appInfo; + private final NotificationsPreferences _notificationsPrefs; + private final JavaMailSenderImpl _javaMailSender; + private final XnatAppInfo _appInfo; + private final SerializerService _serializer; } diff --git a/src/main/java/org/nrg/xapi/rest/settings/SiteConfigApi.java b/src/main/java/org/nrg/xapi/rest/settings/SiteConfigApi.java index cf1a48b90c5ce8c45bb6e3aa40452f100d914479..56fbf94e19288ca61e1b6338c6f5be50ec18d0a7 100644 --- a/src/main/java/org/nrg/xapi/rest/settings/SiteConfigApi.java +++ b/src/main/java/org/nrg/xapi/rest/settings/SiteConfigApi.java @@ -8,13 +8,14 @@ import org.nrg.prefs.exceptions.InvalidPreferenceName; import org.nrg.xapi.exceptions.InitializationException; import org.nrg.xdat.preferences.SiteConfigPreferences; import org.nrg.xdat.rest.AbstractXapiRestController; +import org.nrg.xdat.security.services.RoleHolder; +import org.nrg.xdat.security.services.UserManagementServiceI; import org.nrg.xnat.services.XnatAppInfo; import org.nrg.xnat.turbine.utils.ArcSpecManager; import org.nrg.xnat.utils.XnatHttpUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -35,8 +36,8 @@ import java.util.Properties; @RequestMapping(value = "/siteConfig") public class SiteConfigApi extends AbstractXapiRestController { @Autowired - @Lazy - public SiteConfigApi(final SiteConfigPreferences preferences, final XnatAppInfo appInfo) { + public SiteConfigApi(final SiteConfigPreferences preferences, final UserManagementServiceI userManagementService, final RoleHolder roleHolder, final XnatAppInfo appInfo) { + super(userManagementService, roleHolder); _preferences = preferences; _appInfo = appInfo; } @@ -169,14 +170,6 @@ public class SiteConfigApi extends AbstractXapiRestController { return new ResponseEntity<>(status); } - if (_log.isInfoEnabled()) { - final StringBuilder message = new StringBuilder("User ").append(getSessionUser().getUsername()).append(" is setting the values for the following properties:\n"); - for (final String name : properties.keySet()) { - message.append(" * ").append(name).append(": ").append(properties.get(name)).append("\n"); - } - _log.info(message.toString()); - } - // Is this call initializing the system? final boolean isInitializing = properties.containsKey("initialized") && StringUtils.equals("true", properties.get("initialized")); for (final String name : properties.keySet()) { diff --git a/src/main/java/org/nrg/xapi/rest/theme/ThemeApi.java b/src/main/java/org/nrg/xapi/rest/theme/ThemeApi.java index 69fb69b2ea31264d4335e4a46919402db4d984ac..b650d8d041644e8fe8c2071e1ddb7462e5f51376 100644 --- a/src/main/java/org/nrg/xapi/rest/theme/ThemeApi.java +++ b/src/main/java/org/nrg/xapi/rest/theme/ThemeApi.java @@ -16,17 +16,16 @@ import org.apache.commons.io.FileUtils; import org.nrg.framework.annotations.XapiRestController; import org.nrg.xapi.rest.NotFoundException; import org.nrg.xdat.entities.ThemeConfig; -import org.nrg.xdat.security.XDATUser; +import org.nrg.xdat.rest.AbstractXapiRestController; import org.nrg.xdat.security.services.RoleHolder; +import org.nrg.xdat.security.services.UserManagementServiceI; import org.nrg.xdat.services.ThemeService; -import org.nrg.xft.security.UserI; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -41,27 +40,28 @@ import java.util.List; @Api(description = "XNAT Theme Management API") @XapiRestController @RequestMapping(value = "/theme") -public class ThemeApi { - private static final Logger _log = LoggerFactory.getLogger(ThemeApi.class); - +public class ThemeApi extends AbstractXapiRestController { @Autowired - private ThemeService themeService; + public ThemeApi(final ThemeService themeService, final UserManagementServiceI userManagementService, final RoleHolder roleHolder) { + super(userManagementService, roleHolder); + _themeService = themeService; + } @ApiOperation(value = "Get the currently selected global theme or a role based theme if specified.", notes = "Use this to get the theme selected by the system administrator on the Theme Management page.", response = ThemeConfig.class, responseContainer = "ThemeConfig") @ApiResponses({@ApiResponse(code = 200, message = "Reports the currently selected global theme (if there is one) and whether or not it's enabled."), @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"/{role}"}, produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE}, method = RequestMethod.GET) public ResponseEntity<ThemeConfig> themeGet(@ApiParam(value = "\"global\" or role name of currently set theme", required = true) @PathVariable("role") String role) { - if("global".equalsIgnoreCase(role)){ - return new ResponseEntity<>(themeService.getTheme(), HttpStatus.OK); + if ("global".equalsIgnoreCase(role)) { + return new ResponseEntity<>(_themeService.getTheme(), HttpStatus.OK); } - return new ResponseEntity<>(themeService.getTheme(role), HttpStatus.OK); + return new ResponseEntity<>(_themeService.getTheme(role), HttpStatus.OK); } @ApiOperation(value = "Get list of available themes.", notes = "Use this to get a list of all available themes on the XNAT system.", response = ThemeService.TypeOption.class, responseContainer = "List") @ApiResponses({@ApiResponse(code = 200, message = "Reports the currently selected global theme (if there is one), whether or not it's enabled, and a list of available themes on the system in a JSON string."), @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE}, method = RequestMethod.GET) public ResponseEntity<List<ThemeService.TypeOption>> themesGet() { - return new ResponseEntity<>(themeService.loadExistingThemes(), HttpStatus.OK); + return new ResponseEntity<>(_themeService.loadExistingThemes(), HttpStatus.OK); } @ApiOperation(value = "Deletes the theme with the specified name.", notes = "Returns success on deletion. ", response = String.class) @@ -73,20 +73,20 @@ public class ThemeApi { if (status != null) { return new ResponseEntity<>(status); } - if("null".equals(theme)){ + if ("null".equals(theme)) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } - if(!themeService.themeExists(theme)) { + if (!_themeService.themeExists(theme)) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } try { - File f = new File(themeService.getThemesPath() + File.separator + theme); + File f = new File(_themeService.getThemesPath() + File.separator + theme); FileUtils.deleteDirectory(f); - if(!f.exists()) { - themeConfig = themeService.getTheme(); + if (!f.exists()) { + themeConfig = _themeService.getTheme(); String themeName = (themeConfig != null) ? themeConfig.getName() : null; if (theme.equals(themeName)) { - themeService.setTheme((ThemeConfig) null); + _themeService.setTheme((ThemeConfig) null); } } } catch (Exception e) { @@ -99,7 +99,7 @@ public class ThemeApi { @ApiOperation(value = "Sets the current global theme to the one specified.", notes = "Returns the updated serialized theme object.", response = ThemeConfig.class) @ApiResponses({@ApiResponse(code = 200, message = "Successfully updated the current global theme."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to create or update this user."), @ApiResponse(code = 404, message = "Theme not found."), @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"/{theme}"}, produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_HTML_VALUE}, method = {RequestMethod.PUT}) - public ResponseEntity<ThemeConfig> themePut(@ApiParam(value = "The name of the theme to select.", required = true) @PathVariable("theme") String theme, @RequestParam(value = "enabled", required=false, defaultValue="true") String enabled) throws NotFoundException { + public ResponseEntity<ThemeConfig> themePut(@ApiParam(value = "The name of the theme to select.", required = true) @PathVariable("theme") String theme, @RequestParam(value = "enabled", required = false, defaultValue = "true") String enabled) throws NotFoundException { HttpStatus status = isPermitted(); if (status != null) { return new ResponseEntity<>(status); @@ -107,15 +107,15 @@ public class ThemeApi { ThemeConfig themeConfig; try { boolean themeEnabled = true; - if("false".equalsIgnoreCase(enabled)){ + if ("false".equalsIgnoreCase(enabled)) { themeEnabled = false; } - if("null".equals(theme)){ + if ("null".equals(theme)) { theme = null; } - themeConfig = themeService.setTheme(theme, themeEnabled); + themeConfig = _themeService.setTheme(theme, themeEnabled); } catch (ThemeService.ThemeNotFoundException e) { - _log.error(e.getInvalidTheme()+" not found."); + _log.error(e.getInvalidTheme() + " not found."); return new ResponseEntity<>(HttpStatus.NOT_FOUND); } catch (Exception e) { _log.error("An error occurred setting the theme " + theme, e); @@ -127,19 +127,19 @@ public class ThemeApi { @ApiOperation(value = "Accepts a multipart form with a zip file upload and extracts its contents in the theme system folder. If successful, the first (root) directory name (or theme name) unzipped is returned in the response. This will overwrite any other directories already existing with the same name without warning.", notes = "The structure of the zipped package must have only directories at it's root.", response = String.class) @ApiResponses({@ApiResponse(code = 200, message = "Theme package successfully uploaded and extracted."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to upload a theme package."), @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.POST}) - public ResponseEntity<List<ThemeService.TypeOption>> themePostUpload(@ApiParam(value = "Multipart file object being uploaded", required = true) @RequestParam(value="themePackage", required=false) MultipartFile themePackage) { + public ResponseEntity<List<ThemeService.TypeOption>> themePostUpload(@ApiParam(value = "Multipart file object being uploaded", required = true) @RequestParam(value = "themePackage", required = false) MultipartFile themePackage) { HttpStatus status = isPermitted(); if (status != null) { return new ResponseEntity<>(status); } List<ThemeService.TypeOption> themeOptions = new ArrayList<>(); try { - if(!themePackage.getContentType().contains("zip")) { + if (!themePackage.getContentType().contains("zip")) { String error = "No valid files were uploaded. Theme package must be of type: application/x-zip-compressed"; _log.error(error); return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } - List<String> dirs = themeService.extractTheme(themePackage.getInputStream()); + List<String> dirs = _themeService.extractTheme(themePackage.getInputStream()); for (String dir : dirs) { themeOptions.add(new ThemeService.TypeOption(dir, dir)); } @@ -157,22 +157,7 @@ public class ThemeApi { return new ResponseEntity<>(themeOptions, HttpStatus.OK); } - private UserI getSessionUser() { - Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); - if ((principal instanceof UserI)) { - return (UserI) principal; - } - return null; - } - - private HttpStatus isPermitted() { - UserI sessionUser = getSessionUser(); - if ((sessionUser instanceof XDATUser)) { - return _roleHolder.isSiteAdmin(sessionUser) ? null : HttpStatus.FORBIDDEN; - } - return null; - } + private static final Logger _log = LoggerFactory.getLogger(ThemeApi.class); - @Autowired - private RoleHolder _roleHolder; + private final ThemeService _themeService; } diff --git a/src/main/java/org/nrg/xapi/rest/users/UsersApi.java b/src/main/java/org/nrg/xapi/rest/users/UsersApi.java index dca118723dd34a2b6b3cadeaeccd3a6b51d6d261..af585d8c80e2f35922d1229670e6e85925e9a3a5 100644 --- a/src/main/java/org/nrg/xapi/rest/users/UsersApi.java +++ b/src/main/java/org/nrg/xapi/rest/users/UsersApi.java @@ -3,6 +3,8 @@ package org.nrg.xapi.rest.users; import io.swagger.annotations.*; import org.apache.commons.lang3.StringUtils; import org.nrg.framework.annotations.XapiRestController; +import org.nrg.framework.exceptions.NrgServiceError; +import org.nrg.framework.exceptions.NrgServiceRuntimeException; import org.nrg.xapi.model.users.User; import org.nrg.xapi.rest.NotFoundException; import org.nrg.xdat.XDAT; @@ -12,6 +14,7 @@ import org.nrg.xdat.security.UserGroupI; import org.nrg.xdat.security.helpers.Groups; import org.nrg.xdat.security.helpers.Users; import org.nrg.xdat.security.services.RoleHolder; +import org.nrg.xdat.security.services.UserManagementServiceI; import org.nrg.xdat.security.user.exceptions.UserInitException; import org.nrg.xdat.security.user.exceptions.UserNotFoundException; import org.nrg.xdat.services.AliasTokenService; @@ -21,7 +24,6 @@ import org.nrg.xft.security.UserI; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -33,7 +35,11 @@ import java.util.*; @XapiRestController @RequestMapping(value = "/users") public class UsersApi extends AbstractXapiRestController { - private static final Logger _log = LoggerFactory.getLogger(UsersApi.class); + @Autowired + public UsersApi(final SiteConfigPreferences preferences, final UserManagementServiceI userManagementService, final RoleHolder roleHolder) { + super(userManagementService, roleHolder); + _preferences = preferences; + } @ApiOperation(value = "Get list of users.", notes = "The primary users function returns a list of all users of the XNAT system.", response = User.class, responseContainer = "List") @ApiResponses({@ApiResponse(code = 200, message = "An array of users"), @ApiResponse(code = 500, message = "Unexpected error")}) @@ -60,21 +66,24 @@ public class UsersApi extends AbstractXapiRestController { return new ResponseEntity<>(status); } } - List<UserI> users = Users.getUsers(); - List<Map<String, String>> userMaps = new ArrayList<>(); - for (UserI user : users) { - try { - Map<String, String> userMap = new HashMap<>(); - userMap.put("firstname", user.getFirstname()); - userMap.put("lastname", user.getLastname()); - userMap.put("username", user.getUsername()); - userMap.put("email", user.getEmail()); - userMap.put("id", String.valueOf(user.getID())); - userMap.put("enabled", String.valueOf(user.isEnabled())); - userMap.put("verified", String.valueOf(user.isVerified())); - userMaps.add(userMap); - } catch (Exception e) { - _log.error("", e); + List<UserI> users = getUserManagementService().getUsers(); + List<Map<String, String>> userMaps = null; + if (users != null && users.size() > 0) { + userMaps = new ArrayList<>(); + for (UserI user : users) { + try { + Map<String, String> userMap = new HashMap<>(); + userMap.put("firstname", user.getFirstname()); + userMap.put("lastname", user.getLastname()); + userMap.put("username", user.getUsername()); + userMap.put("email", user.getEmail()); + userMap.put("id", String.valueOf(user.getID())); + userMap.put("enabled", String.valueOf(user.isEnabled())); + userMap.put("verified", String.valueOf(user.isVerified())); + userMaps.add(userMap); + } catch (Exception e) { + _log.error("", e); + } } } return new ResponseEntity<>(userMaps, HttpStatus.OK); @@ -90,7 +99,7 @@ public class UsersApi extends AbstractXapiRestController { } final UserI user; try { - user = Users.getUser(id); + user = getUserManagementService().getUser(id); return user == null ? new ResponseEntity<User>(HttpStatus.NOT_FOUND) : new ResponseEntity<>(new User(user), HttpStatus.OK); } catch (UserInitException e) { _log.error("An error occurred initializing the user " + id, e); @@ -110,12 +119,15 @@ public class UsersApi extends AbstractXapiRestController { } UserI user; try { - user = Users.getUser(username); + user = getUserManagementService().getUser(username); } catch (Exception e) { //Create new User - user = Users.createUser(); + user = getUserManagementService().createUser(); user.setLogin(username); } + if (user == null) { + throw new NrgServiceRuntimeException(NrgServiceError.Unknown, "Failed to retrieve or create a user object for user " + username); + } if ((StringUtils.isNotBlank(model.getFirstName())) && (!StringUtils.equals(model.getFirstName(), user.getFirstname()))) { user.setFirstname(model.getFirstName()); } @@ -141,7 +153,7 @@ public class UsersApi extends AbstractXapiRestController { } try { - Users.save(user, getSessionUser(), false, new EventDetails(EventUtils.CATEGORY.DATA, EventUtils.TYPE.WEB_SERVICE, Event.Modified, "", "")); + getUserManagementService().save(user, getSessionUser(), false, new EventDetails(EventUtils.CATEGORY.DATA, EventUtils.TYPE.WEB_SERVICE, Event.Modified, "", "")); return new ResponseEntity<>(HttpStatus.OK); } catch (Exception e) { _log.error("Error occurred modifying user " + user.getLogin()); @@ -158,7 +170,7 @@ public class UsersApi extends AbstractXapiRestController { return new ResponseEntity<>(status); } try { - final UserI user = Users.getUser(id); + final UserI user = getUserManagementService().getUser(id); if (user == null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } @@ -180,13 +192,13 @@ public class UsersApi extends AbstractXapiRestController { return new ResponseEntity<>(status); } try { - final UserI user = Users.getUser(id); + final UserI user = getUserManagementService().getUser(id); if (user == null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } user.setEnabled(flag); try { - Users.save(user, getSessionUser(), false, new EventDetails(EventUtils.CATEGORY.DATA, EventUtils.TYPE.WEB_SERVICE, flag ? Event.Enabled : Event.Disabled, "", "")); + getUserManagementService().save(user, getSessionUser(), false, new EventDetails(EventUtils.CATEGORY.DATA, EventUtils.TYPE.WEB_SERVICE, flag ? Event.Enabled : Event.Disabled, "", "")); return new ResponseEntity<>(HttpStatus.OK); } catch (Exception e) { _log.error("Error occurred " + (flag ? "enabling" : "disabling") + " user " + user.getLogin()); @@ -209,7 +221,7 @@ public class UsersApi extends AbstractXapiRestController { return new ResponseEntity<>(status); } try { - final UserI user = Users.getUser(id); + final UserI user = getUserManagementService().getUser(id); if (user == null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } @@ -231,13 +243,13 @@ public class UsersApi extends AbstractXapiRestController { return new ResponseEntity<>(status); } try { - final UserI user = Users.getUser(id); + final UserI user = getUserManagementService().getUser(id); if (user == null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } user.setVerified(flag); try { - Users.save(user, getSessionUser(), false, new EventDetails(EventUtils.CATEGORY.DATA, EventUtils.TYPE.WEB_SERVICE, flag ? Event.Enabled : Event.Disabled, "", "")); + getUserManagementService().save(user, getSessionUser(), false, new EventDetails(EventUtils.CATEGORY.DATA, EventUtils.TYPE.WEB_SERVICE, flag ? Event.Enabled : Event.Disabled, "", "")); return new ResponseEntity<>(HttpStatus.OK); } catch (Exception e) { _log.error("Error occurred " + (flag ? "enabling" : "disabling") + " user " + user.getLogin()); @@ -254,41 +266,30 @@ public class UsersApi extends AbstractXapiRestController { @ApiOperation(value = "Returns the roles for the user with the specified user ID.", notes = "Returns a collection of the user's roles.", response = Collection.class) @ApiResponses({@ApiResponse(code = 200, message = "User roles successfully retrieved."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to view this user."), @ApiResponse(code = 404, message = "User not found."), @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(value = {"/profiles/{id}/roles"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET}) - public ResponseEntity<Collection<String>> usersIdRolesGet(@ApiParam(value = "The ID of the user to retrieve the roles for.", required = true) @PathVariable("id") String id) { + public ResponseEntity<Collection<String>> usersIdRolesGet(@ApiParam(value = "The ID of the user to retrieve the roles for.", required = true) @PathVariable("id") final String id) { HttpStatus status = isPermitted(id); if (status != null) { return new ResponseEntity<>(status); } - try { - final UserI user = Users.getUser(id); - if (user == null) { - return new ResponseEntity<>(HttpStatus.NOT_FOUND); - } - Collection<String> roles = _roleHolder.getRoles(user); - return new ResponseEntity<>(roles, HttpStatus.OK); - } catch (UserInitException e) { - _log.error("An error occurred initializing the user " + id, e); - return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); - } catch (UserNotFoundException e) { - return new ResponseEntity<>(HttpStatus.NOT_FOUND); - } + final Collection<String> roles = getUserRoles(id); + return roles != null ? new ResponseEntity<>(roles, HttpStatus.OK) : new ResponseEntity<Collection<String>>(HttpStatus.FORBIDDEN); } @ApiOperation(value = "Adds a role to a user.", notes = "Assigns a new role to a user.", response = Boolean.class) @ApiResponses({@ApiResponse(code = 200, message = "User role successfully added."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to enable or disable this user."), @ApiResponse(code = 404, message = "User not found."), @ApiResponse(code = 500, message = "Unexpected error")}) - @RequestMapping(value = {"/profiles/{id}/addrole/{role}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT}) + @RequestMapping(value = {"/profiles/{id}/roles/{role}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT}) public ResponseEntity<Boolean> usersIdAddRole(@ApiParam(value = "ID of the user to add a role to", required = true) @PathVariable("id") String id, @ApiParam(value = "The user's new role.", required = true) @PathVariable("role") String role) { HttpStatus status = isPermitted(id); if (status != null) { return new ResponseEntity<>(status); } try { - final UserI user = Users.getUser(id); + final UserI user = getUserManagementService().getUser(id); if (user == null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } try { - _roleHolder.addRole(getSessionUser(), user, role); + getRoleHolder().addRole(getSessionUser(), user, role); return new ResponseEntity<>(HttpStatus.OK); } catch (Exception e) { _log.error("Error occurred adding role " + role + " to user " + user.getLogin() + "."); @@ -302,22 +303,21 @@ public class UsersApi extends AbstractXapiRestController { } } - @ApiOperation(value = "Remove a user's role.", notes = "Removes a user's role.", response = Boolean.class) @ApiResponses({@ApiResponse(code = 200, message = "User role successfully removed."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to enable or disable this user."), @ApiResponse(code = 404, message = "User not found."), @ApiResponse(code = 500, message = "Unexpected error")}) - @RequestMapping(value = {"/profiles/{id}/removerole/{role}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT}) + @RequestMapping(value = {"/profiles/{id}/roles/{role}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.DELETE}) public ResponseEntity<Boolean> usersIdRemoveRole(@ApiParam(value = "ID of the user to delete a role from", required = true) @PathVariable("id") String id, @ApiParam(value = "The user role to delete.", required = true) @PathVariable("role") String role) { HttpStatus status = isPermitted(id); if (status != null) { return new ResponseEntity<>(status); } try { - final UserI user = Users.getUser(id); + final UserI user = getUserManagementService().getUser(id); if (user == null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } try { - _roleHolder.deleteRole(getSessionUser(), user, role); + getRoleHolder().deleteRole(getSessionUser(), user, role); return new ResponseEntity<>(HttpStatus.OK); } catch (Exception e) { _log.error("Error occurred removing role " + role + " from user " + user.getLogin() + "."); @@ -340,7 +340,7 @@ public class UsersApi extends AbstractXapiRestController { return new ResponseEntity<>(status); } try { - final UserI user = Users.getUser(id); + final UserI user = getUserManagementService().getUser(id); if (user == null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } @@ -356,14 +356,14 @@ public class UsersApi extends AbstractXapiRestController { @ApiOperation(value = "Adds a user to a group.", notes = "Assigns user to a group.", response = Boolean.class) @ApiResponses({@ApiResponse(code = 200, message = "User successfully added to group."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to enable or disable this user."), @ApiResponse(code = 404, message = "User not found."), @ApiResponse(code = 500, message = "Unexpected error")}) - @RequestMapping(value = {"/profiles/{id}/addgroup/{group}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT}) - public ResponseEntity<Boolean> usersIdAddGroup(@ApiParam(value = "ID of the user to add to a group", required = true) @PathVariable("id") String id, @ApiParam(value = "The user's new group.", required = true) @PathVariable("group") String group) { + @RequestMapping(value = {"/profiles/{id}/groups/{group}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT}) + public ResponseEntity<Boolean> usersIdAddGroup(@ApiParam(value = "ID of the user to add to a group", required = true) @PathVariable("id") String id, @ApiParam(value = "The user's new group.", required = true) @PathVariable("group") final String group) { HttpStatus status = isPermitted(id); if (status != null) { return new ResponseEntity<>(status); } try { - final UserI user = Users.getUser(id); + final UserI user = getUserManagementService().getUser(id); if (user == null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } @@ -382,17 +382,16 @@ public class UsersApi extends AbstractXapiRestController { } } - @ApiOperation(value = "Removes a user from a group.", notes = "Removes a user from a group.", response = Boolean.class) @ApiResponses({@ApiResponse(code = 200, message = "User's group successfully removed."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to enable or disable this user."), @ApiResponse(code = 404, message = "User not found."), @ApiResponse(code = 500, message = "Unexpected error")}) - @RequestMapping(value = {"/profiles/{id}/removegroup/{group}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT}) - public ResponseEntity<Boolean> usersIdRemoveGroup(@ApiParam(value = "ID of the user to remove from group", required = true) @PathVariable("id") String id, @ApiParam(value = "The group to remove the user from.", required = true) @PathVariable("group") String group) { + @RequestMapping(value = {"/profiles/{id}/groups/{group}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.DELETE}) + public ResponseEntity<Boolean> usersIdRemoveGroup(@ApiParam(value = "ID of the user to remove from group", required = true) @PathVariable("id") final String id, @ApiParam(value = "The group to remove the user from.", required = true) @PathVariable("group") final String group) { HttpStatus status = isPermitted(id); if (status != null) { return new ResponseEntity<>(status); } try { - final UserI user = Users.getUser(id); + final UserI user = getUserManagementService().getUser(id); if (user == null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } @@ -425,10 +424,7 @@ public class UsersApi extends AbstractXapiRestController { public static String VerifiedEmail = "Verified User Email"; } - @Autowired - @Lazy - private SiteConfigPreferences _preferences; + private static final Logger _log = LoggerFactory.getLogger(UsersApi.class); - @Autowired - private RoleHolder _roleHolder; + private final SiteConfigPreferences _preferences; } diff --git a/src/main/java/org/nrg/xnat/archive/GradualDicomImporter.java b/src/main/java/org/nrg/xnat/archive/GradualDicomImporter.java index 624e22c10383124b0d4126547e8bc15f5d998ad6..27c700dbe4ac8262c057236939a9cbfe2fe87d3b 100644 --- a/src/main/java/org/nrg/xnat/archive/GradualDicomImporter.java +++ b/src/main/java/org/nrg/xnat/archive/GradualDicomImporter.java @@ -70,56 +70,20 @@ import java.util.concurrent.Callable; @ImporterHandler(handler = ImporterHandlerA.GRADUAL_DICOM_IMPORTER) public class GradualDicomImporter extends ImporterHandlerA { public static final String SENDER_AE_TITLE_PARAM = "Sender-AE-Title"; - public static final String SENDER_ID_PARAM = "Sender-ID"; - public static final String TSUID_PARAM = "Transfer-Syntax-UID"; - public static final int LAST_TAG = Tag.SeriesDescription; - - private static final Logger logger = LoggerFactory.getLogger(GradualDicomImporter.class); - private static final Object NOT_A_WRITABLE_PROJECT = new Object(); - private static final String DEFAULT_TRANSFER_SYNTAX = TransferSyntax.ImplicitVRLittleEndian.uid(); - private static final String RENAME_PARAM = "rename"; - private static final DicomFileNamer DEFAULT_NAMER = new SOPHashDicomFileNamer(); - private static final long PROJECT_CACHE_EXPIRY_SECONDS = 120; - private static final boolean canDecompress = initializeCanDecompress(); - private static final CacheManager cacheManager = CacheManager.getInstance(); - private DicomFilterService _filterService; - - private static boolean initializeCanDecompress() { - try { - return Decompress.isSupported(); - } catch (NoClassDefFoundError error) { - return false; - } - } - - private final FileWriterWrapperI fw; - private final UserI user; - private final Map<String, Object> params; - private DicomObjectIdentifier<XnatProjectdata> dicomObjectIdentifier; - private DicomFileNamer namer = DEFAULT_NAMER; - private TransferSyntax ts = null; - private Cache projectCache = null; + public static final String SENDER_ID_PARAM = "Sender-ID"; + public static final String TSUID_PARAM = "Transfer-Syntax-UID"; public GradualDicomImporter(final Object listenerControl, - final UserI u, - final FileWriterWrapperI fw, - final Map<String, Object> params) + final UserI user, + final FileWriterWrapperI fileWriter, + final Map<String, Object> parameters) throws IOException, ClientException { - super(listenerControl, u, fw, params); - this.user = u; - this.fw = fw; - this.params = params; - if (params.containsKey(TSUID_PARAM)) { - ts = TransferSyntax.valueOf((String) params.get(TSUID_PARAM)); - } - } - - private boolean canCreateIn(final XnatProjectdata p) { - try { - return PrearcUtils.canModify(user, p.getId()); - } catch (Throwable t) { - logger.error("Unable to check permissions for " + user + " in " + p, t); - return false; + super(listenerControl, user, fileWriter, parameters); + _user = user; + _fileWriter = fileWriter; + _parameters = parameters; + if (_parameters.containsKey(TSUID_PARAM)) { + _transferSyntax = TransferSyntax.valueOf((String) _parameters.get(TSUID_PARAM)); } } @@ -128,6 +92,7 @@ public class GradualDicomImporter extends ImporterHandlerA { * does not map to a project where user has create perms? * * @param e cache element (not null) + * * @return true if this element indicates no writable project */ public static boolean isCachedNotWriteableProject(final Element e) { @@ -146,114 +111,52 @@ public class GradualDicomImporter extends ImporterHandlerA { cache.put(new Element(name, NOT_A_WRITABLE_PROJECT)); } - private XnatProjectdata getProject(final Object alias, final Callable<XnatProjectdata> lookupProject) { - if (null == projectCache) { - projectCache = getUserProjectCache(user); - } - if (null != alias) { - logger.debug("looking for project matching alias {} from query parameters", alias); - final Element pe = projectCache.get(alias); - if (null != pe) { - if (isCachedNotWriteableProject(pe)) { - // this alias is cached as a non-writable project name, but user is specifying it. - // maybe they know something we don't; clear cache entry so we can try again. - projectCache.remove(alias); - return getProject(alias, lookupProject); - } else { - return (XnatProjectdata) pe.getObjectValue(); - } + /** + * Adds a cache of project objects on a per-user basis. This is currently used by GradualDicomImporter and DbBackedProjectIdentifier + * + * @param user The user for whom to retrieve the cache. + * + * @return The user's cache. + */ + public static Cache getUserProjectCache(final UserI user) { + final String cacheName = user.getLogin() + "-projects"; + synchronized (cacheManager) { + if (!cacheManager.cacheExists(cacheName)) { + final CacheConfiguration config = new CacheConfiguration(cacheName, 0) + .copyOnRead(false).copyOnWrite(false) + .eternal(false) + .persistence(new PersistenceConfiguration().strategy(PersistenceConfiguration.Strategy.NONE)) + .timeToLiveSeconds(PROJECT_CACHE_EXPIRY_SECONDS); + final Cache cache = new Cache(config); + cacheManager.addCache(cache); + return cache; } else { - logger.trace("cache miss for project alias {}, trying database", alias); - final XnatProjectdata p = XnatProjectdata.getXnatProjectdatasById(alias, user, false); - if (null != p && canCreateIn(p)) { - projectCache.put(new Element(alias, p)); - return p; - } else { - for (final XnatProjectdata pa : - XnatProjectdata.getXnatProjectdatasByField("xnat:projectData/aliases/alias/alias", - alias, user, false)) { - if (canCreateIn(pa)) { - projectCache.put(new Element(alias, pa)); - return pa; - } - } - } - } - logger.info("storage request specified project {}, which does not exist or user does not have create perms", alias); - } else { - logger.trace("no project alias found in query parameters"); - } - // No alias, or we couldn't match it to a project. Run the identifier to see if that can get a project name/alias. - // (Don't cache alias->identifier-derived-project because we didn't use the alias to derive the project.) - try { - return null == lookupProject ? null : lookupProject.call(); - } catch (Throwable t) { - logger.error("error in project lookup", t); - return null; - } - } - - private File getSafeFile(File sessionDir, String scan, String name, DicomObject o, boolean forceRename) { - String fileName = namer.makeFileName(o); - while (fileName.charAt(0) == '.') { - fileName = fileName.substring(1); - } - final File safeFile = Files.getImageFile(sessionDir, scan, fileName); - if (forceRename) { - return safeFile; - } - final String valname = Files.toFileNameChars(name); - if (!Files.isValidFilename(valname)) { - return safeFile; - } - final File reqFile = Files.getImageFile(sessionDir, scan, valname); - if (reqFile.exists()) { - try (final FileInputStream fin = new FileInputStream(reqFile)) { - final DicomObject o1 = read(fin, name); - if (Objects.equal(o.get(Tag.SOPInstanceUID), o1.get(Tag.SOPInstanceUID)) && - Objects.equal(o.get(Tag.SOPClassUID), o1.get(Tag.SOPClassUID))) { - return reqFile; // object are equivalent; ok to overwrite - } else { - return safeFile; - } - } catch (Throwable t) { - return safeFile; + return cacheManager.getCache(cacheName); } - } else { - return reqFile; - } - } - - private static <K, V> String getString(final Map<K, V> m, final K k, final V defaultValue) { - final V v = m.get(k); - if (null == v) { - return null == defaultValue ? null : defaultValue.toString(); - } else { - return v.toString(); } } @Override public List<String> call() throws ClientException, ServerException { - final String name = fw.getName(); + final String name = _fileWriter.getName(); final DicomObject dicom; final XnatProjectdata project; - try (final BufferedInputStream bis = new BufferedInputStream(fw.getInputStream()); - final DicomInputStream dis = null == ts ? new DicomInputStream(bis) : new DicomInputStream(bis, ts)) { - final int lastTag = Math.max(dicomObjectIdentifier.getTags().last(), LAST_TAG) + 1; + try (final BufferedInputStream bis = new BufferedInputStream(_fileWriter.getInputStream()); + final DicomInputStream dis = null == _transferSyntax ? new DicomInputStream(bis) : new DicomInputStream(bis, _transferSyntax)) { + final int lastTag = Math.max(_dicomObjectIdentifier.getTags().last(), Tag.SeriesDescription) + 1; logger.trace("reading object into memory up to {}", TagUtils.toString(lastTag)); dis.setHandler(new StopTagInputHandler(lastTag)); dicom = dis.readDicomObject(); - logger.trace("handling file with query parameters {}", params); + logger.trace("handling file with query parameters {}", _parameters); try { // project identifier is expensive, so avoid if possible - project = getProject(PrearcUtils.identifyProject(params), - new Callable<XnatProjectdata>() { - public XnatProjectdata call() { - return dicomObjectIdentifier.getProject(dicom); - } - }); + project = getProject(PrearcUtils.identifyProject(_parameters), + new Callable<XnatProjectdata>() { + public XnatProjectdata call() { + return _dicomObjectIdentifier.getProject(dicom); + } + }); } catch (MalformedURLException e1) { logger.error("unable to parse supplied destination flag", e1); throw new ClientException(Status.CLIENT_ERROR_BAD_REQUEST, e1); @@ -276,11 +179,11 @@ public class GradualDicomImporter extends ImporterHandlerA { } if (!(shouldIncludeDicomObject(siteFilter, dicom) && shouldIncludeDicomObject(projectFilter, dicom))) { return new ArrayList<>(); - /** TODO: Return information to user on rejected files. Unfortunately throwing an - * exception causes DicomBrowser to display a panicked error message. Some way of - * returning the information that a particular file type was not accepted would be - * nice, though. Possibly record the information and display on an admin page. - * Work to be done for 1.7 + /* TODO: Return information to user on rejected files. Unfortunately throwing an + * exception causes DicomBrowser to display a panicked error message. Some way of + * returning the information that a particular file type was not accepted would be + * nice, though. Possibly record the information and display on an admin page. + * Work to be done for 1.7 */ } try { @@ -307,22 +210,19 @@ public class GradualDicomImporter extends ImporterHandlerA { //root = new File(project.getPrearchivePath()); root = new File(ArcSpecManager.GetInstance().getGlobalPrearchivePath() + "/" + project.getId()); } - final File tsdir, sessdir; - - tsdir = new File(root, PrearcUtils.makeTimestamp()); String sessionLabel; - if (params.containsKey(URIManager.EXPT_LABEL)) { - sessionLabel = (String) params.get(URIManager.EXPT_LABEL); - logger.trace("using provided experiment label {}", params.get(URIManager.EXPT_LABEL)); + if (_parameters.containsKey(URIManager.EXPT_LABEL)) { + sessionLabel = (String) _parameters.get(URIManager.EXPT_LABEL); + logger.trace("using provided experiment label {}", _parameters.get(URIManager.EXPT_LABEL)); } else { - sessionLabel = dicomObjectIdentifier.getSessionLabel(dicom); + sessionLabel = _dicomObjectIdentifier.getSessionLabel(dicom); } String visit; - if (params.containsKey(URIManager.VISIT_LABEL)) { - visit = (String) params.get(URIManager.VISIT_LABEL); - logger.trace("using provided visit label {}", params.get(URIManager.VISIT_LABEL)); + if (_parameters.containsKey(URIManager.VISIT_LABEL)) { + visit = (String) _parameters.get(URIManager.VISIT_LABEL); + logger.trace("using provided visit label {}", _parameters.get(URIManager.VISIT_LABEL)); } else { visit = null; } @@ -332,13 +232,17 @@ public class GradualDicomImporter extends ImporterHandlerA { } final String subject; - if (params.containsKey(URIManager.SUBJECT_ID)) { - subject = (String) params.get(URIManager.SUBJECT_ID); + if (_parameters.containsKey(URIManager.SUBJECT_ID)) { + subject = (String) _parameters.get(URIManager.SUBJECT_ID); } else { - subject = dicomObjectIdentifier.getSubjectLabel(dicom); + subject = _dicomObjectIdentifier.getSubjectLabel(dicom); } + + + final File timestamp = new File(root, PrearcUtils.makeTimestamp()); + if (null == subject) { - logger.trace("subject is null for session {}/{}", tsdir, sessionLabel); + logger.trace("subject is null for session {}/{}", timestamp, sessionLabel); } session = new SessionData(); @@ -348,14 +252,14 @@ public class GradualDicomImporter extends ImporterHandlerA { session.setVisit(visit); session.setScan_date(dicom.getDate(Tag.StudyDate)); session.setTag(studyInstanceUID); - session.setTimestamp(tsdir.getName()); + session.setTimestamp(timestamp.getName()); session.setStatus(PrearcUtils.PrearcStatus.RECEIVING); session.setLastBuiltDate(Calendar.getInstance().getTime()); session.setSubject(subject); - session.setUrl((new File(tsdir, sessionLabel)).getAbsolutePath()); - session.setSource(params.get(URIManager.SOURCE)); - session.setPreventAnon(Boolean.valueOf((String) params.get(URIManager.PREVENT_ANON))); - session.setPreventAutoCommit(Boolean.valueOf((String) params.get(URIManager.PREVENT_AUTO_COMMIT))); + session.setUrl((new File(timestamp, sessionLabel)).getAbsolutePath()); + session.setSource(_parameters.get(URIManager.SOURCE)); + session.setPreventAnon(Boolean.valueOf((String) _parameters.get(URIManager.PREVENT_ANON))); + session.setPreventAutoCommit(Boolean.valueOf((String) _parameters.get(URIManager.PREVENT_AUTO_COMMIT))); // Query the cache for an existing session that has this Study Instance UID, project name, and optional modality. // If found the SessionData object we just created is over-ridden with the values from the cache. @@ -364,10 +268,9 @@ public class GradualDicomImporter extends ImporterHandlerA { // // This record is necessary so that, if this row was created by this call, it can be deleted if anonymization // goes wrong. In case of any other error the file is left on the filesystem. - // TODO: This is where things are going awry with Jenny's prearchive bug. Either<SessionData, SessionData> getOrCreate; try { - getOrCreate = PrearcDatabase.eitherGetOrCreateSession(session, tsdir, shouldAutoArchive(project, dicom)); + getOrCreate = PrearcDatabase.eitherGetOrCreateSession(session, timestamp, shouldAutoArchive(project, dicom)); if (getOrCreate.isLeft()) { session = getOrCreate.getLeft(); } else { @@ -378,26 +281,17 @@ public class GradualDicomImporter extends ImporterHandlerA { } try { - //if the status isn't RECEIVING, fix it - //else if the last mod time is more then 15 seconds ago, update it. - //this code builds and executes the sql directly, because the APIs for doing so generate multiple SELECT statements (to confirm the row is there) - //we've confirmed the row is there in line 338, so that shouldn't be necessary here. + // else if the last mod time is more then 15 seconds ago, update it. + // this code builds and executes the sql directly, because the APIs for doing so generate multiple SELECT statements (to confirm the row is there) + // we've confirmed the row is there in line 338, so that shouldn't be necessary here. // this code executes for every file received, so any unnecessary sql should be eliminated. - if (!PrearcUtils.PrearcStatus.RECEIVING.equals(session.getStatus())) { - //update the last modified time and set the status - PoolDBUtils.ExecuteNonSelectQuery(DatabaseSession.updateSessionStatusSQL(session.getName(), session.getTimestamp(), session.getProject(), PrearcUtils.PrearcStatus.RECEIVING), null, null); - } else if (Calendar.getInstance().getTime().after(DateUtils.addSeconds(session.getLastBuiltDate(), 15))) { + if (Calendar.getInstance().getTime().after(DateUtils.addSeconds(session.getLastBuiltDate(), 15))) { PoolDBUtils.ExecuteNonSelectQuery(DatabaseSession.updateSessionLastModSQL(session.getName(), session.getTimestamp(), session.getProject()), null, null); } } catch (Exception e) { - logger.error("An error occurred while trying to set the session status to RECEIVING", e); - //not exactly sure what we should do here. should we throw an exception, and the received file won't be stored locally? Or should we let it go and let the file be saved but unreferenced. - //the old code threw an exception, so we'll keep that logic. - throw new ServerException("An error occurred while trying to set the session status to RECEIVING", e); + logger.error("An error occurred trying to update the session update timestamp.", e); } - sessdir = new File(new File(root, session.getTimestamp()), session.getFolderName()); - // Build the scan label final String seriesNum = dicom.getString(Tag.SeriesNumber); final String seriesUID = dicom.getString(Tag.SeriesInstanceUID); @@ -410,7 +304,7 @@ public class GradualDicomImporter extends ImporterHandlerA { scan = null; } - final String source = getString(params, SENDER_ID_PARAM, user.getLogin()); + final String source = getString(_parameters, SENDER_ID_PARAM, _user.getLogin()); final DicomObject fmi; if (dicom.contains(Tag.TransferSyntaxUID)) { @@ -419,26 +313,26 @@ public class GradualDicomImporter extends ImporterHandlerA { final String sopClassUID = dicom.getString(Tag.SOPClassUID); final String sopInstanceUID = dicom.getString(Tag.SOPInstanceUID); final String transferSyntaxUID; - if (null == ts) { + if (null == _transferSyntax) { transferSyntaxUID = dicom.getString(Tag.TransferSyntaxUID, DEFAULT_TRANSFER_SYNTAX); } else { - transferSyntaxUID = ts.uid(); + transferSyntaxUID = _transferSyntax.uid(); } fmi = new BasicDicomObject(); fmi.initFileMetaInformation(sopClassUID, sopInstanceUID, transferSyntaxUID); - if (params.containsKey(SENDER_AE_TITLE_PARAM)) { - fmi.putString(Tag.SourceApplicationEntityTitle, VR.AE, (String) params.get(SENDER_AE_TITLE_PARAM)); + if (_parameters.containsKey(SENDER_AE_TITLE_PARAM)) { + fmi.putString(Tag.SourceApplicationEntityTitle, VR.AE, (String) _parameters.get(SENDER_AE_TITLE_PARAM)); } } - final File f = getSafeFile(sessdir, scan, name, dicom, Boolean.valueOf((String) params.get(RENAME_PARAM))); - f.getParentFile().mkdirs(); + final File sessionFolder = new File(new File(root, session.getTimestamp()), session.getFolderName()); + final File outputFile = getSafeFile(sessionFolder, scan, name, dicom, Boolean.valueOf((String) _parameters.get(RENAME_PARAM))); + outputFile.getParentFile().mkdirs(); - PrearcUtils.PrearcFileLock lock; + final PrearcUtils.PrearcFileLock lock; try { - lock = PrearcUtils.lockFile(session.getSessionDataTriple(), f.getName()); - write(fmi, dicom, bis, f, source); - + lock = PrearcUtils.lockFile(session.getSessionDataTriple(), outputFile.getName()); + write(fmi, dicom, bis, outputFile, source); } catch (IOException e) { throw new ServerException(Status.SERVER_ERROR_INSUFFICIENT_STORAGE, e); } catch (SessionFileLockException e) { @@ -446,35 +340,35 @@ public class GradualDicomImporter extends ImporterHandlerA { } try { // check to see of this session came in through the upload applet - if (!session.getPreventAnon() && AnonUtils.getService().isEnabled(DicomEdit.buildScriptPath(DicomEdit.ResourceScope.SITE_WIDE, null),null)) { + if (!session.getPreventAnon() && AnonUtils.getService().isEnabled(DicomEdit.buildScriptPath(DicomEdit.ResourceScope.SITE_WIDE, null), null)) { Configuration c = AnonUtils.getCachedSitewideAnon(); if (c != null && c.getStatus().equals(Configuration.ENABLED_STRING)) { //noinspection deprecation - Anonymize.anonymize(f, - session.getProject(), - session.getSubject(), - session.getFolderName(), - true, - c.getId(), - c.getContents()); + Anonymize.anonymize(outputFile, + session.getProject(), + session.getSubject(), + session.getFolderName(), + true, + c.getId(), + c.getContents()); } else { logger.debug("Anonymization is not enabled, allowing session {} {} {} to proceed without anonymization.", session.getProject(), session.getSubject(), session.getName()); } - } else if(session.getPreventAnon()){ + } else if (session.getPreventAnon()) { logger.debug("The session {} {} {} has already been anonymized by the uploader, proceeding without further anonymization.", session.getProject(), session.getSubject(), session.getName()); } } catch (Throwable e) { - logger.debug("Dicom anonymization failed: " + f, e); + logger.debug("Dicom anonymization failed: " + outputFile, e); try { // if we created a row in the database table for this session // delete it. if (getOrCreate.isRight()) { PrearcDatabase.deleteSession(session.getFolderName(), session.getTimestamp(), session.getProject()); } else { - f.delete(); + outputFile.delete(); } } catch (Throwable t) { - logger.debug("Unable to delete relevant file :" + f, e); + logger.debug("Unable to delete relevant file :" + outputFile, e); throw new ServerException(Status.SERVER_ERROR_INTERNAL, t); } throw new ServerException(Status.SERVER_ERROR_INTERNAL, e); @@ -496,6 +390,103 @@ public class GradualDicomImporter extends ImporterHandlerA { } + public GradualDicomImporter setIdentifier(final DicomObjectIdentifier<XnatProjectdata> dicomObjectIdentifier) { + _dicomObjectIdentifier = dicomObjectIdentifier; + return this; + } + + public GradualDicomImporter setNamer(final DicomFileNamer namer) { + _fileNamer = namer; + return this; + } + + private boolean canCreateIn(final XnatProjectdata p) { + try { + return PrearcUtils.canModify(_user, p.getId()); + } catch (Throwable t) { + logger.error("Unable to check permissions for " + _user + " in " + p, t); + return false; + } + } + + private XnatProjectdata getProject(final Object alias, final Callable<XnatProjectdata> lookupProject) { + if (null == _projectCache) { + _projectCache = getUserProjectCache(_user); + } + if (null != alias) { + logger.debug("looking for project matching alias {} from query parameters", alias); + final Element pe = _projectCache.get(alias); + if (null != pe) { + if (isCachedNotWriteableProject(pe)) { + // this alias is cached as a non-writable project name, but user is specifying it. + // maybe they know something we don't; clear cache entry so we can try again. + _projectCache.remove(alias); + return getProject(alias, lookupProject); + } else { + return (XnatProjectdata) pe.getObjectValue(); + } + } else { + logger.trace("cache miss for project alias {}, trying database", alias); + final XnatProjectdata p = XnatProjectdata.getXnatProjectdatasById(alias, _user, false); + if (null != p && canCreateIn(p)) { + _projectCache.put(new Element(alias, p)); + return p; + } else { + for (final XnatProjectdata pa : + XnatProjectdata.getXnatProjectdatasByField("xnat:projectData/aliases/alias/alias", + alias, _user, false)) { + if (canCreateIn(pa)) { + _projectCache.put(new Element(alias, pa)); + return pa; + } + } + } + } + logger.info("storage request specified project {}, which does not exist or user does not have create perms", alias); + } else { + logger.trace("no project alias found in query parameters"); + } + // No alias, or we couldn't match it to a project. Run the identifier to see if that can get a project name/alias. + // (Don't cache alias->identifier-derived-project because we didn't use the alias to derive the project.) + try { + return null == lookupProject ? null : lookupProject.call(); + } catch (Throwable t) { + logger.error("error in project lookup", t); + return null; + } + } + + private File getSafeFile(File sessionDir, String scan, String name, DicomObject o, boolean forceRename) { + String fileName = _fileNamer.makeFileName(o); + while (fileName.charAt(0) == '.') { + fileName = fileName.substring(1); + } + final File safeFile = Files.getImageFile(sessionDir, scan, fileName); + if (forceRename) { + return safeFile; + } + final String valname = Files.toFileNameChars(name); + if (!Files.isValidFilename(valname)) { + return safeFile; + } + final File reqFile = Files.getImageFile(sessionDir, scan, valname); + if (reqFile.exists()) { + try (final FileInputStream fin = new FileInputStream(reqFile)) { + final DicomObject o1 = read(fin, name); + if (Objects.equal(o.get(Tag.SOPInstanceUID), o1.get(Tag.SOPInstanceUID)) && + Objects.equal(o.get(Tag.SOPClassUID), o1.get(Tag.SOPClassUID))) { + return reqFile; // object are equivalent; ok to overwrite + } else { + return safeFile; + } + } catch (Throwable t) { + return safeFile; + } + } else { + return reqFile; + } + } + private boolean shouldIncludeDicomObject(final SeriesImportFilter filter, final DicomObject dicom) { // If we don't have a filter or the filter is turned off, then we include the DICOM object by default (no filtering) if (filter == null || !filter.isEnabled()) { @@ -522,48 +513,30 @@ public class GradualDicomImporter extends ImporterHandlerA { if (null == project) { return null; } - Boolean fromDicomObject = dicomObjectIdentifier.requestsAutoarchive(o); + Boolean fromDicomObject = _dicomObjectIdentifier.requestsAutoarchive(o); if (fromDicomObject != null) { return fromDicomObject ? PrearchiveCode.AutoArchive : PrearchiveCode.Manual; } return PrearchiveCode.code(project.getArcSpecification().getPrearchiveCode()); } - /** - * Adds a cache of project objects on a per-user basis. This is currently used by GradualDicomImporter and DbBackedProjectIdentifier - * - * @param user The user for whom to retrieve the cache. - * @return The user's cache. - */ - public static Cache getUserProjectCache(final UserI user) { - final String cacheName = user.getLogin() + "-projects"; - synchronized (cacheManager) { - if (!cacheManager.cacheExists(cacheName)) { - final CacheConfiguration config = new CacheConfiguration(cacheName, 0) - .copyOnRead(false).copyOnWrite(false) - .eternal(false) - .persistence(new PersistenceConfiguration().strategy(PersistenceConfiguration.Strategy.NONE)) - .timeToLiveSeconds(PROJECT_CACHE_EXPIRY_SECONDS); - final Cache cache = new Cache(config); - cacheManager.addCache(cache); - return cache; - } else { - return cacheManager.getCache(cacheName); - } + private static boolean initializeCanDecompress() { + try { + return Decompress.isSupported(); + } catch (NoClassDefFoundError error) { + return false; } } - public GradualDicomImporter setIdentifier(final DicomObjectIdentifier<XnatProjectdata> identifier) { - this.dicomObjectIdentifier = identifier; - return this; - } - - public GradualDicomImporter setNamer(final DicomFileNamer namer) { - this.namer = namer; - return this; + private static <K, V> String getString(final Map<K, V> m, final K k, final V defaultValue) { + final V v = m.get(k); + if (null == v) { + return null == defaultValue ? null : defaultValue.toString(); + } else { + return v.toString(); + } } - private static DicomObject read(final InputStream in, final String name) throws ClientException { try (final BufferedInputStream bis = new BufferedInputStream(in); final DicomInputStream dis = new DicomInputStream(bis)) { @@ -580,12 +553,7 @@ public class GradualDicomImporter extends ImporterHandlerA { } } - private static Logger slog() { - return LoggerFactory.getLogger(GradualDicomImporter.class); - } - - private static void write(final DicomObject fmi, final DicomObject dataset, - BufferedInputStream remainder, final File f, final String source) + private static void write(final DicomObject fmi, final DicomObject dataset, final BufferedInputStream remainder, final File f, final String source) throws ClientException, IOException { IOException ioexception = null; final FileOutputStream fos = new FileOutputStream(f); @@ -604,7 +572,7 @@ public class GradualDicomImporter extends ImporterHandlerA { } catch (IOException e) { ioexception = e; throw new ClientException(Status.CLIENT_ERROR_BAD_REQUEST, - "error parsing DICOM object", e); + "error parsing DICOM object", e); } final ByteArrayInputStream bis = new ByteArrayInputStream(Decompress.dicomObject2Bytes(dataset, tsuid)); final DicomObject d = Decompress.decompress_image(bis, tsuid); @@ -617,7 +585,7 @@ public class GradualDicomImporter extends ImporterHandlerA { if (t instanceof IOException) { ioexception = (IOException) t; } else { - slog().error("Unable to write decompressed dataset", t); + logger.error("Unable to write decompressed dataset", t); } try { dos.close(); @@ -628,12 +596,12 @@ public class GradualDicomImporter extends ImporterHandlerA { } catch (ClientException e) { throw e; } catch (Throwable t) { - slog().error("Decompression failed; storing in original format " + tsuid, t); + logger.error("Decompression failed; storing in original format " + tsuid, t); dos.writeFileMetaInformation(fmi); dos.writeDataset(dataset, tsuid); if (null != remainder) { final long copied = ByteStreams.copy(remainder, bos); - slog().trace("copied {} additional bytes to {}", copied, f); + logger.trace("copied {} additional bytes to {}", copied, f); } } } else { @@ -641,16 +609,16 @@ public class GradualDicomImporter extends ImporterHandlerA { dos.writeDataset(dataset, tsuid); if (null != remainder) { final long copied = ByteStreams.copy(remainder, bos); - slog().trace("copied {} additional bytes to {}", copied, f); + logger.trace("copied {} additional bytes to {}", copied, f); } } } catch (NoClassDefFoundError t) { - slog().error("Unable to check compression status; storing in original format " + tsuid, t); + logger.error("Unable to check compression status; storing in original format " + tsuid, t); dos.writeFileMetaInformation(fmi); dos.writeDataset(dataset, tsuid); if (null != remainder) { final long copied = ByteStreams.copy(remainder, bos); - slog().trace("copied {} additional bytes to {}", copied, f); + logger.trace("copied {} additional bytes to {}", copied, f); } } } catch (IOException e) { @@ -673,4 +641,24 @@ public class GradualDicomImporter extends ImporterHandlerA { } } } + + private static final Logger logger = LoggerFactory.getLogger(GradualDicomImporter.class); + private static final Object NOT_A_WRITABLE_PROJECT = new Object(); + private static final String DEFAULT_TRANSFER_SYNTAX = TransferSyntax.ImplicitVRLittleEndian.uid(); + private static final String RENAME_PARAM = "rename"; + private static final DicomFileNamer DEFAULT_NAMER = new SOPHashDicomFileNamer(); + private static final long PROJECT_CACHE_EXPIRY_SECONDS = 120; + private static final boolean canDecompress = initializeCanDecompress(); + private static final CacheManager cacheManager = CacheManager.getInstance(); + + private final FileWriterWrapperI _fileWriter; + private final UserI _user; + private final Map<String, Object> _parameters; + + private DicomFileNamer _fileNamer = DEFAULT_NAMER; + + private TransferSyntax _transferSyntax; + private Cache _projectCache; + private DicomFilterService _filterService; + private DicomObjectIdentifier<XnatProjectdata> _dicomObjectIdentifier; } diff --git a/src/main/java/org/nrg/xnat/configuration/ApplicationConfig.java b/src/main/java/org/nrg/xnat/configuration/ApplicationConfig.java index fc87250dc16ca6e9219b38b3529ae256e3a211ad..8d3a1e5d8a952bc9144164497ca0b17b53f709de 100644 --- a/src/main/java/org/nrg/xnat/configuration/ApplicationConfig.java +++ b/src/main/java/org/nrg/xnat/configuration/ApplicationConfig.java @@ -1,8 +1,8 @@ package org.nrg.xnat.configuration; import org.nrg.config.exceptions.SiteConfigurationException; -import org.nrg.framework.services.ContextService; -import org.nrg.xdat.preferences.InitializerSiteConfiguration; +import org.nrg.config.services.ConfigService; +import org.nrg.framework.services.NrgEventService; import org.nrg.xdat.preferences.NotificationsPreferences; import org.nrg.xdat.preferences.SiteConfigPreferences; import org.nrg.xdat.security.HistoricPasswordValidator; @@ -12,14 +12,16 @@ import org.nrg.xdat.security.XDATUserMgmtServiceImpl; import org.nrg.xdat.security.services.UserManagementServiceI; import org.nrg.xdat.services.ThemeService; import org.nrg.xdat.services.impl.ThemeServiceImpl; +import org.nrg.xnat.initialization.InitializingTask; import org.nrg.xnat.initialization.InitializingTasksExecutor; +import org.nrg.xnat.restlet.XnatRestlet; import org.nrg.xnat.restlet.XnatRestletExtensions; +import org.nrg.xnat.restlet.XnatRestletExtensionsBean; import org.nrg.xnat.restlet.actions.importer.ImporterHandlerPackages; import org.nrg.xnat.services.PETTracerUtils; import org.nrg.xnat.utils.XnatUserProvider; import org.springframework.context.annotation.*; -import javax.inject.Inject; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; @@ -41,29 +43,23 @@ public class ApplicationConfig { } @Bean - @Primary - public ContextService contextService() { - return ContextService.getInstance(); + public InitializingTasksExecutor initializingTasksExecutor(final List<InitializingTask> tasks) { + return new InitializingTasksExecutor(tasks); } @Bean - public InitializingTasksExecutor initializingTasksExecutor() { - return new InitializingTasksExecutor(); + public SiteConfigPreferences siteConfigPreferences(final NrgEventService service) { + return new SiteConfigPreferences(service); } @Bean - public SiteConfigPreferences siteConfigPreferences() { - return new SiteConfigPreferences(); + public NotificationsPreferences notificationsPreferences(final NrgEventService service) { + return new NotificationsPreferences(service); } @Bean - public NotificationsPreferences notificationsPreferences() { - return new NotificationsPreferences(); - } - - @Bean - public PETTracerUtils petTracerUtils() throws Exception { - return new PETTracerUtils(); + public PETTracerUtils petTracerUtils(final ConfigService configService) throws Exception { + return new PETTracerUtils(configService); } @Bean @@ -94,21 +90,32 @@ public class ApplicationConfig { } @Bean - public XnatUserProvider receivedFileUserProvider() throws SiteConfigurationException { - final String receivedFileUser = _preferences.getReceivedFileUser(); - return new XnatUserProvider(receivedFileUser); + public XnatUserProvider primaryAdminUserProvider(final SiteConfigPreferences preferences) throws SiteConfigurationException { + return new XnatUserProvider(preferences.getPrimaryAdminUsername()); } @Bean - public XnatRestletExtensions xnatRestletExtensions() { + public XnatUserProvider receivedFileUserProvider(final SiteConfigPreferences preferences) throws SiteConfigurationException { + return new XnatUserProvider(preferences.getReceivedFileUser()); + } + + @Bean + public XnatRestletExtensionsBean xnatRestletExtensionsBean(final List<XnatRestletExtensions> extensions) { + return new XnatRestletExtensionsBean(extensions); + } + + @Bean + public XnatRestletExtensions defaultXnatRestletExtensions() { return new XnatRestletExtensions(new HashSet<>(Arrays.asList(new String[] {"org.nrg.xnat.restlet.extensions"}))); } + @Bean + public XnatRestletExtensions extraXnatRestletExtensions() { + return new XnatRestletExtensions(new HashSet<>(Arrays.asList(new String[] {"org.nrg.xnat.restlet.actions"}))); + } + @Bean public ImporterHandlerPackages importerHandlerPackages() { return new ImporterHandlerPackages(new HashSet<>(Arrays.asList(new String[] {"org.nrg.xnat.restlet.actions", "org.nrg.xnat.archive"}))); } - - @Inject - private InitializerSiteConfiguration _preferences; } diff --git a/src/main/java/org/nrg/xnat/configuration/DicomImportConfig.java b/src/main/java/org/nrg/xnat/configuration/DicomImportConfig.java index 3485070631c2ba8d98472da424807b975656bdb6..1a8aa273d59b8b51bdd17558a83b21dcc09c508c 100644 --- a/src/main/java/org/nrg/xnat/configuration/DicomImportConfig.java +++ b/src/main/java/org/nrg/xnat/configuration/DicomImportConfig.java @@ -21,9 +21,9 @@ import java.util.List; @ComponentScan("org.nrg.dcm.preferences") public class DicomImportConfig { @Bean - public DicomObjectIdentifier<XnatProjectdata> dicomObjectIdentifier(final XnatUserProvider provider) { + public DicomObjectIdentifier<XnatProjectdata> dicomObjectIdentifier(final XnatUserProvider receivedFileUserProvider) { final ClassicDicomObjectIdentifier identifier = new ClassicDicomObjectIdentifier(); - identifier.setUserProvider(provider); + identifier.setUserProvider(receivedFileUserProvider); return identifier; } diff --git a/src/main/java/org/nrg/xnat/configuration/FeaturesConfig.java b/src/main/java/org/nrg/xnat/configuration/FeaturesConfig.java index 8731059b15e79897648a91e0c0fa52c3f4ee8363..913d610b05cc91db69e2b7e46a53a8de3e60314c 100644 --- a/src/main/java/org/nrg/xnat/configuration/FeaturesConfig.java +++ b/src/main/java/org/nrg/xnat/configuration/FeaturesConfig.java @@ -2,15 +2,13 @@ package org.nrg.xnat.configuration; import org.apache.commons.lang3.StringUtils; import org.nrg.config.exceptions.SiteConfigurationException; -import org.nrg.xdat.preferences.InitializerSiteConfiguration; +import org.nrg.xdat.preferences.SiteConfigPreferences; import org.nrg.xdat.security.services.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import javax.inject.Inject; - import static org.nrg.xdat.security.services.FeatureRepositoryServiceI.DEFAULT_FEATURE_REPO_SERVICE; import static org.nrg.xdat.security.services.FeatureServiceI.DEFAULT_FEATURE_SERVICE; import static org.nrg.xdat.security.services.RoleRepositoryServiceI.DEFAULT_ROLE_REPO_SERVICE; @@ -20,8 +18,8 @@ import static org.nrg.xdat.security.services.RoleServiceI.DEFAULT_ROLE_SERVICE; public class FeaturesConfig { @Bean - public FeatureServiceI featureService() throws ClassNotFoundException, IllegalAccessException, InstantiationException, SiteConfigurationException { - final String serviceImpl = StringUtils.defaultIfBlank(_preferences.getFeatureService(), DEFAULT_FEATURE_SERVICE); + public FeatureServiceI featureService(final SiteConfigPreferences preferences) throws ClassNotFoundException, IllegalAccessException, InstantiationException, SiteConfigurationException { + final String serviceImpl = StringUtils.defaultIfBlank(preferences.getFeatureService(), DEFAULT_FEATURE_SERVICE); if (_log.isDebugEnabled()) { _log.debug("Creating feature service with implementing class " + serviceImpl); } @@ -29,8 +27,8 @@ public class FeaturesConfig { } @Bean - public FeatureRepositoryServiceI featureRepositoryService() throws ClassNotFoundException, IllegalAccessException, InstantiationException, SiteConfigurationException { - final String serviceImpl = StringUtils.defaultIfBlank(_preferences.getFeatureRepositoryService(), DEFAULT_FEATURE_REPO_SERVICE); + public FeatureRepositoryServiceI featureRepositoryService(final SiteConfigPreferences preferences) throws ClassNotFoundException, IllegalAccessException, InstantiationException, SiteConfigurationException { + final String serviceImpl = StringUtils.defaultIfBlank(preferences.getFeatureRepositoryService(), DEFAULT_FEATURE_REPO_SERVICE); if (_log.isDebugEnabled()) { _log.debug("Creating feature repository service with implementing class " + serviceImpl); } @@ -38,8 +36,8 @@ public class FeaturesConfig { } @Bean - public RoleHolder roleService() throws ClassNotFoundException, IllegalAccessException, InstantiationException, SiteConfigurationException { - final String serviceImpl = StringUtils.defaultIfBlank(_preferences.getRoleService(), DEFAULT_ROLE_SERVICE); + public RoleHolder roleService(final SiteConfigPreferences preferences) throws ClassNotFoundException, IllegalAccessException, InstantiationException, SiteConfigurationException { + final String serviceImpl = StringUtils.defaultIfBlank(preferences.getRoleService(), DEFAULT_ROLE_SERVICE); if (_log.isDebugEnabled()) { _log.debug("Creating role service with implementing class " + serviceImpl); } @@ -47,8 +45,8 @@ public class FeaturesConfig { } @Bean - public RoleRepositoryHolder roleRepositoryService() throws ClassNotFoundException, IllegalAccessException, InstantiationException, SiteConfigurationException { - final String serviceImpl = StringUtils.defaultIfBlank(_preferences.getRoleRepositoryService(), DEFAULT_ROLE_REPO_SERVICE); + public RoleRepositoryHolder roleRepositoryService(final SiteConfigPreferences preferences) throws ClassNotFoundException, IllegalAccessException, InstantiationException, SiteConfigurationException { + final String serviceImpl = StringUtils.defaultIfBlank(preferences.getRoleRepositoryService(), DEFAULT_ROLE_REPO_SERVICE); if (_log.isDebugEnabled()) { _log.debug("Creating role repository service with implementing class " + serviceImpl); } @@ -56,7 +54,4 @@ public class FeaturesConfig { } private static final Logger _log = LoggerFactory.getLogger(FeaturesConfig.class); - - @Inject - private InitializerSiteConfiguration _preferences; } diff --git a/src/main/java/org/nrg/xnat/configuration/NotificationsConfig.java b/src/main/java/org/nrg/xnat/configuration/NotificationsConfig.java index 888f8c18e9925d2ee848c0a8484043abf6e5b660..57ba799583e9d3814908d0c5c9ee1da4cf729fd5 100644 --- a/src/main/java/org/nrg/xnat/configuration/NotificationsConfig.java +++ b/src/main/java/org/nrg/xnat/configuration/NotificationsConfig.java @@ -3,16 +3,17 @@ package org.nrg.xnat.configuration; import org.apache.commons.lang3.StringUtils; import org.nrg.config.exceptions.SiteConfigurationException; import org.nrg.framework.orm.hibernate.HibernateEntityPackageList; +import org.nrg.mail.services.MailService; import org.nrg.notify.entities.ChannelRendererProvider; import org.nrg.notify.renderers.ChannelRenderer; import org.nrg.notify.renderers.NrgMailChannelRenderer; -import org.nrg.xdat.preferences.InitializerSiteConfiguration; +import org.nrg.xdat.preferences.NotificationsPreferences; +import org.nrg.xdat.preferences.SiteConfigPreferences; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.mail.javamail.JavaMailSenderImpl; -import javax.inject.Inject; import java.io.IOException; import java.util.Collections; import java.util.HashMap; @@ -24,8 +25,8 @@ import java.util.Properties; public class NotificationsConfig { @Bean - public JavaMailSenderImpl mailSender() throws IOException, SiteConfigurationException { - final Map<String, String> smtp = _preferences.getSmtpServer(); + public JavaMailSenderImpl mailSender(final NotificationsPreferences preferences) throws IOException, SiteConfigurationException { + final Map<String, String> smtp = preferences.getSmtpServer(); final JavaMailSenderImpl sender = new JavaMailSenderImpl(); if(smtp!=null) { sender.setHost(StringUtils.defaultIfBlank(smtp.remove("host"), "localhost")); @@ -50,28 +51,20 @@ public class NotificationsConfig { } @Bean - public ChannelRenderer mailChannelRenderer() throws SiteConfigurationException { - final NrgMailChannelRenderer renderer = new NrgMailChannelRenderer(); - renderer.setFromAddress(_preferences.getAdminEmail()); - renderer.setSubjectPrefix(_preferences.getEmailPrefix()); + public NrgMailChannelRenderer mailChannelRenderer(final SiteConfigPreferences siteConfigPreferences, final NotificationsPreferences notificationsPreferences, final MailService mailService) throws SiteConfigurationException { + final NrgMailChannelRenderer renderer = new NrgMailChannelRenderer(mailService); + renderer.setFromAddress(siteConfigPreferences.getAdminEmail()); + renderer.setSubjectPrefix(notificationsPreferences.getEmailPrefix()); return renderer; } @Bean - public Map<String, ChannelRenderer> renderers() throws SiteConfigurationException { - final Map<String, ChannelRenderer> renderers = new HashMap<>(); - renderers.put("htmlMail", mailChannelRenderer()); - renderers.put("textMail", mailChannelRenderer()); - return renderers; - } - - @Bean - public ChannelRendererProvider rendererProvider() throws SiteConfigurationException { + public ChannelRendererProvider rendererProvider(final NrgMailChannelRenderer renderer) throws SiteConfigurationException { final ChannelRendererProvider provider = new ChannelRendererProvider(); - provider.setRenderers(renderers()); + final Map<String, ChannelRenderer> renderers = new HashMap<>(); + renderers.put("htmlMail", renderer); + renderers.put("textMail", renderer); + provider.setRenderers(renderers); return provider; } - - @Inject - private InitializerSiteConfiguration _preferences; } diff --git a/src/main/java/org/nrg/xnat/configuration/OrmConfig.java b/src/main/java/org/nrg/xnat/configuration/OrmConfig.java index 6a43ca8d6f3f5221dff2a91f1e8d3871b8ad83ac..fd4aa23e9eb2c13b47bf0aa0be2c7f24294165ff 100644 --- a/src/main/java/org/nrg/xnat/configuration/OrmConfig.java +++ b/src/main/java/org/nrg/xnat/configuration/OrmConfig.java @@ -1,15 +1,12 @@ package org.nrg.xnat.configuration; -import com.google.common.base.Joiner; -import org.apache.commons.io.IOUtils; import org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory; import org.hibernate.cache.spi.RegionFactory; import org.hibernate.cfg.ImprovedNamingStrategy; import org.nrg.framework.exceptions.NrgServiceError; import org.nrg.framework.exceptions.NrgServiceException; +import org.nrg.framework.orm.hibernate.AggregatedAnnotationSessionFactoryBean; import org.nrg.framework.orm.hibernate.PrefixedTableNamingStrategy; -import org.nrg.framework.processors.XnatPluginBean; -import org.nrg.framework.utilities.BasicXnatResourceLocator; import org.nrg.framework.utilities.Beans; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,32 +14,30 @@ import org.springframework.beans.factory.config.PropertiesFactoryBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; -import org.springframework.core.io.Resource; import org.springframework.orm.hibernate4.HibernateTransactionManager; import org.springframework.orm.hibernate4.LocalSessionFactoryBean; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; -import javax.inject.Inject; import javax.sql.DataSource; import java.io.IOException; -import java.io.InputStream; -import java.util.HashSet; import java.util.Properties; -import java.util.Set; @Configuration @EnableTransactionManagement(proxyTargetClass = true) public class OrmConfig { + + public static final String XNAT_ENTITIES_PACKAGES = "META-INF/xnat/entities/**/*-entity-packages.txt"; + @Bean public ImprovedNamingStrategy namingStrategy() { return new PrefixedTableNamingStrategy("xhbm"); } @Bean - public PropertiesFactoryBean hibernateProperties() { + public PropertiesFactoryBean hibernateProperties(final Environment environment) { final PropertiesFactoryBean bean = new PropertiesFactoryBean(); - final Properties properties = Beans.getNamespacedProperties(_environment, "hibernate", false); + final Properties properties = Beans.getNamespacedProperties(environment, "hibernate", false); if (properties.size() == 0) { if (_log.isDebugEnabled()) { final StringBuilder message = new StringBuilder("No Hibernate properties specified, using default properties:\n"); @@ -58,30 +53,21 @@ public class OrmConfig { } @Bean - public RegionFactory regionFactory() throws NrgServiceException { + public RegionFactory regionFactory(final Environment environment) throws NrgServiceException { try { - return new SingletonEhCacheRegionFactory(hibernateProperties().getObject()); + return new SingletonEhCacheRegionFactory(hibernateProperties(environment).getObject()); } catch (IOException e) { throw new NrgServiceException(NrgServiceError.Unknown, "An error occurred trying to retrieve the Hibernate properties", e); } } @Bean - public LocalSessionFactoryBean sessionFactory(final DataSource dataSource) throws NrgServiceException { + public LocalSessionFactoryBean sessionFactory(final Environment environment, final DataSource dataSource) throws NrgServiceException { try { - final LocalSessionFactoryBean bean = new LocalSessionFactoryBean(); - final String[] packages = getXnatEntityPackages(); - if (_log.isDebugEnabled()) { - final StringBuilder message = new StringBuilder("The following packages will be scanned for persistent entities:\n"); - for (final String packageName : packages) { - message.append(" * ").append(packageName).append("\n"); - } - _log.debug(message.toString()); - } - bean.setPackagesToScan(packages); + final AggregatedAnnotationSessionFactoryBean bean = new AggregatedAnnotationSessionFactoryBean(XNAT_ENTITIES_PACKAGES); bean.setDataSource(dataSource); - bean.setCacheRegionFactory(regionFactory()); - bean.setHibernateProperties(hibernateProperties().getObject()); + bean.setCacheRegionFactory(regionFactory(environment)); + bean.setHibernateProperties(hibernateProperties(environment).getObject()); bean.setNamingStrategy(namingStrategy()); return bean; } catch (IOException e) { @@ -90,31 +76,8 @@ public class OrmConfig { } @Bean - public PlatformTransactionManager transactionManager(final DataSource dataSource) throws NrgServiceException { - return new HibernateTransactionManager(sessionFactory(dataSource).getObject()); - } - - private static String[] getXnatEntityPackages() throws IOException { - final Set<String> packages = new HashSet<>(); - for (final Resource resource : BasicXnatResourceLocator.getResources("classpath*:META-INF/xnat/entities/**/*-entity-packages.txt")) { - if (_log.isDebugEnabled()) { - _log.debug("Processing entity packages from the resource: {}", resource.getFilename()); - } - try (final InputStream input = resource.getInputStream()) { - packages.addAll(IOUtils.readLines(input, "UTF-8")); - } - } - try { - for (final XnatPluginBean plugin : XnatPluginBean.findAllXnatPluginBeans()) { - if (_log.isDebugEnabled()) { - _log.debug("Processing entity packages from plugin {}: {}", plugin.getId(), Joiner.on(", ").join(plugin.getEntityPackages())); - } - packages.addAll(plugin.getEntityPackages()); - } - } catch (IOException e) { - throw new RuntimeException("An error occurred trying to locate XNAT plugin definitions."); - } - return packages.toArray(new String[packages.size()]); + public PlatformTransactionManager transactionManager(final Environment environment, final DataSource dataSource) throws NrgServiceException { + return new HibernateTransactionManager(sessionFactory(environment, dataSource).getObject()); } private static final Properties DEFAULT_HIBERNATE_PROPERTIES = new Properties() {{ @@ -126,7 +89,4 @@ public class OrmConfig { }}; private static final Logger _log = LoggerFactory.getLogger(OrmConfig.class); - - @Inject - private Environment _environment; } diff --git a/src/main/java/org/nrg/xnat/configuration/PreferencesConfig.java b/src/main/java/org/nrg/xnat/configuration/PreferencesConfig.java index 7154bfe7925b461befc0327ccce506a77180be0f..74329d3464588247db1b896382f394f789b38663 100644 --- a/src/main/java/org/nrg/xnat/configuration/PreferencesConfig.java +++ b/src/main/java/org/nrg/xnat/configuration/PreferencesConfig.java @@ -6,21 +6,24 @@ import org.nrg.config.services.SiteConfigurationService; import org.nrg.config.services.UserConfigurationService; import org.nrg.config.services.impl.DefaultConfigService; import org.nrg.config.services.impl.DefaultUserConfigurationService; -import org.nrg.prefs.configuration.NrgPrefsServiceConfiguration; +import org.nrg.prefs.configuration.NrgPrefsConfiguration; +import org.nrg.prefs.services.NrgPreferenceService; import org.nrg.prefs.services.PreferenceService; -import org.nrg.xdat.preferences.InitializerSiteConfiguration; +import org.nrg.xdat.preferences.SiteConfigPreferences; import org.nrg.xnat.resolvers.XnatPreferenceEntityResolver; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.core.env.Environment; -import javax.inject.Inject; import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; @Configuration @ComponentScan("org.nrg.config.daos") -@Import(NrgPrefsServiceConfiguration.class) +@Import(NrgPrefsConfiguration.class) public class PreferencesConfig { @Bean @@ -30,13 +33,26 @@ public class PreferencesConfig { @Bean public UserConfigurationService userConfigurationService() { - return new DefaultUserConfigurationService(); + return new DefaultUserConfigurationService(configService()); } @Bean - public SiteConfigurationService siteConfigurationService() throws ClassNotFoundException, IllegalAccessException, InstantiationException, SiteConfigurationException { - // TODO: Add direct prefs call or JDBC call to get the site configuration setting from the database. - final Class<? extends SiteConfigurationService> clazz = Class.forName(_preferences.getSiteConfigurationService()).asSubclass(SiteConfigurationService.class); + public SiteConfigurationService siteConfigurationService(final SiteConfigPreferences preferences, final NrgPreferenceService nrgPreferenceService, final ConfigService configService, final Environment environment) throws ClassNotFoundException, IllegalAccessException, InstantiationException, SiteConfigurationException { + final Class<? extends SiteConfigurationService> clazz = Class.forName(preferences.getSiteConfigurationService()).asSubclass(SiteConfigurationService.class); + try { + @SuppressWarnings("unchecked") + final Constructor<? extends SiteConfigurationService> constructor = clazz.getConstructor(NrgPreferenceService.class, Environment.class); + return constructor.newInstance(nrgPreferenceService, environment); + } catch (NoSuchMethodException | InvocationTargetException ignored) { + // No worries, it just doesn't have that constructor. + } + try { + @SuppressWarnings("unchecked") + final Constructor<? extends SiteConfigurationService> constructor = clazz.getConstructor(ConfigService.class, Environment.class); + return constructor.newInstance(configService, environment); + } catch (NoSuchMethodException | InvocationTargetException ignored) { + // No worries, it just doesn't have that constructor. + } return clazz.newInstance(); } @@ -44,7 +60,4 @@ public class PreferencesConfig { public XnatPreferenceEntityResolver defaultResolver(final PreferenceService preferenceService) throws IOException { return new XnatPreferenceEntityResolver(preferenceService); } - - @Inject - private InitializerSiteConfiguration _preferences; } diff --git a/src/main/java/org/nrg/xnat/configuration/ReactorConfig.java b/src/main/java/org/nrg/xnat/configuration/ReactorConfig.java index d831d26cc2c0ac18405ca4c72617f6f4e5ae5750..13f555e7f76d825d7d1b446899c8b3d2ab1d1273 100755 --- a/src/main/java/org/nrg/xnat/configuration/ReactorConfig.java +++ b/src/main/java/org/nrg/xnat/configuration/ReactorConfig.java @@ -17,8 +17,8 @@ import java.util.HashSet; @Configuration public class ReactorConfig { @Bean - public NrgEventService xftEventService() { - return new NrgEventService(); + public NrgEventService eventService(final EventBus eventBus) { + return new NrgEventService(eventBus); } @Bean diff --git a/src/main/java/org/nrg/xnat/configuration/SchedulerConfig.java b/src/main/java/org/nrg/xnat/configuration/SchedulerConfig.java index 991f1ab6f9fc1ede61981b6dc8dbce60609fa3cc..da3c22618e1b3f76d6d52b297e01ca16a516bcf1 100644 --- a/src/main/java/org/nrg/xnat/configuration/SchedulerConfig.java +++ b/src/main/java/org/nrg/xnat/configuration/SchedulerConfig.java @@ -1,17 +1,14 @@ package org.nrg.xnat.configuration; -import org.nrg.config.exceptions.SiteConfigurationException; import org.nrg.framework.services.NrgEventService; import org.nrg.mail.services.EmailRequestLogService; -import org.nrg.xdat.XDAT; -import org.nrg.xdat.preferences.InitializerSiteConfiguration; +import org.nrg.xdat.preferences.NotificationsPreferences; import org.nrg.xdat.preferences.PreferenceEvent; -import org.nrg.xnat.helpers.prearchive.SessionXMLRebuilder; +import org.nrg.xdat.preferences.SiteConfigPreferences; import org.nrg.xnat.security.ResetEmailRequests; -import org.nrg.xnat.utils.XnatUserProvider; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.jms.core.JmsTemplate; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; @@ -19,15 +16,14 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar; import org.springframework.scheduling.config.TriggerTask; import org.springframework.scheduling.support.PeriodicTrigger; -import javax.inject.Inject; import java.util.List; @Configuration @EnableScheduling public class SchedulerConfig implements SchedulingConfigurer { @Bean - public TriggerTask resetEmailRequests() { - return new TriggerTask(new ResetEmailRequests(_emailRequestLogService), new PeriodicTrigger(900000)); + public TriggerTask resetEmailRequests(final EmailRequestLogService service) { + return new TriggerTask(new ResetEmailRequests(service), new PeriodicTrigger(900000)); } @Bean(destroyMethod = "shutdown") @@ -37,43 +33,51 @@ public class SchedulerConfig implements SchedulingConfigurer { return scheduler; } - @Override - public void configureTasks(final ScheduledTaskRegistrar taskRegistrar) { - taskRegistrar.setScheduler(taskScheduler()); - _eventService.triggerEvent(new PreferenceEvent("sessionXmlRebuilderRepeat", String.valueOf(XDAT.getSiteConfigPreferences().getSessionXmlRebuilderRepeat()))); - _eventService.triggerEvent(new PreferenceEvent("aliasTokenTimeout", String.valueOf(XDAT.getSiteConfigPreferences().getAliasTokenTimeout()))); - _eventService.triggerEvent(new PreferenceEvent("inactivityBeforeLockout", String.valueOf(XDAT.getSiteConfigPreferences().getInactivityBeforeLockout()))); - _eventService.triggerEvent(new PreferenceEvent("maxFailedLoginsLockoutDuration", String.valueOf(XDAT.getSiteConfigPreferences().getMaxFailedLoginsLockoutDuration()))); - _eventService.triggerEvent(new PreferenceEvent("emailPrefix", String.valueOf(XDAT.getNotificationsPreferences().getEmailPrefix()))); - _eventService.triggerEvent(new PreferenceEvent("host", String.valueOf(XDAT.getNotificationsPreferences().getHostname()))); - _eventService.triggerEvent(new PreferenceEvent("requireLogin", String.valueOf(XDAT.getSiteConfigPreferences().getRequireLogin()))); - _eventService.triggerEvent(new PreferenceEvent("security.channel", String.valueOf(XDAT.getSiteConfigPreferences().getSecurityChannel()))); - _eventService.triggerEvent(new PreferenceEvent("passwordExpirationType", String.valueOf(XDAT.getSiteConfigPreferences().getPasswordExpirationType()))); - _eventService.triggerEvent(new PreferenceEvent("archivePath", String.valueOf(XDAT.getSiteConfigPreferences().getArchivePath()))); - _eventService.triggerEvent(new PreferenceEvent("security.services.role.default", String.valueOf(XDAT.getSiteConfigPreferences().getRoleService()))); - _eventService.triggerEvent(new PreferenceEvent("checksums", String.valueOf(XDAT.getSiteConfigPreferences().getChecksums()))); - _eventService.triggerEvent(new PreferenceEvent("sitewidePetTracers", String.valueOf(XDAT.getSiteConfigPreferences().getSitewidePetTracers()))); - for (final TriggerTask triggerTask : _triggerTasks) { - taskRegistrar.addTriggerTask(triggerTask); - } + @Autowired + public void setNrgEventService(final NrgEventService service) { + _service = service; } - @Inject - private EmailRequestLogService _emailRequestLogService; + @Autowired + public void setSiteConfigPreferences(final SiteConfigPreferences siteConfigPreferences) { + _siteConfigPreferences = siteConfigPreferences; + } - @Inject - private XnatUserProvider _provider; + @Autowired + public void setNotificationsPreferences(final NotificationsPreferences notificationsPreferences) { + _notificationsPreferences = notificationsPreferences; + } - @Inject - private JmsTemplate _jmsTemplate; + @Autowired + public void setTriggerTasks(final List<TriggerTask> tasks) { + _tasks = tasks; + } - @Inject - private InitializerSiteConfiguration _preferences; + @Override + public void configureTasks(final ScheduledTaskRegistrar taskRegistrar) { + taskRegistrar.setScheduler(taskScheduler()); + + _service.triggerEvent(new PreferenceEvent("sessionXmlRebuilderRepeat", String.valueOf(_siteConfigPreferences.getSessionXmlRebuilderRepeat()))); + _service.triggerEvent(new PreferenceEvent("aliasTokenTimeout", String.valueOf(_siteConfigPreferences.getAliasTokenTimeout()))); + _service.triggerEvent(new PreferenceEvent("inactivityBeforeLockout", String.valueOf(_siteConfigPreferences.getInactivityBeforeLockout()))); + _service.triggerEvent(new PreferenceEvent("maxFailedLoginsLockoutDuration", String.valueOf(_siteConfigPreferences.getMaxFailedLoginsLockoutDuration()))); + _service.triggerEvent(new PreferenceEvent("emailPrefix", String.valueOf(_notificationsPreferences.getEmailPrefix()))); + _service.triggerEvent(new PreferenceEvent("host", String.valueOf(_notificationsPreferences.getHostname()))); + _service.triggerEvent(new PreferenceEvent("requireLogin", String.valueOf(_siteConfigPreferences.getRequireLogin()))); + _service.triggerEvent(new PreferenceEvent("security.channel", String.valueOf(_siteConfigPreferences.getSecurityChannel()))); + _service.triggerEvent(new PreferenceEvent("passwordExpirationType", String.valueOf(_siteConfigPreferences.getPasswordExpirationType()))); + _service.triggerEvent(new PreferenceEvent("archivePath", String.valueOf(_siteConfigPreferences.getArchivePath()))); + _service.triggerEvent(new PreferenceEvent("security.services.role.default", String.valueOf(_siteConfigPreferences.getRoleService()))); + _service.triggerEvent(new PreferenceEvent("checksums", String.valueOf(_siteConfigPreferences.getChecksums()))); + _service.triggerEvent(new PreferenceEvent("sitewidePetTracers", String.valueOf(_siteConfigPreferences.getSitewidePetTracers()))); - @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") - @Inject - private List<TriggerTask> _triggerTasks; + for (final TriggerTask triggerTask : _tasks) { + taskRegistrar.addTriggerTask(triggerTask); + } + } - @Inject - private NrgEventService _eventService; + private SiteConfigPreferences _siteConfigPreferences; + private NotificationsPreferences _notificationsPreferences; + private List<TriggerTask> _tasks; + private NrgEventService _service; } \ No newline at end of file diff --git a/src/main/java/org/nrg/xnat/configuration/WebConfig.java b/src/main/java/org/nrg/xnat/configuration/WebConfig.java index 6780f6787be29cf1daccd58c4de811d94dedd305..2d7c20dde4ece52bcb896832f32a4b0db150e6f3 100644 --- a/src/main/java/org/nrg/xnat/configuration/WebConfig.java +++ b/src/main/java/org/nrg/xnat/configuration/WebConfig.java @@ -1,6 +1,7 @@ package org.nrg.xnat.configuration; import org.nrg.framework.annotations.XapiRestController; +import org.nrg.xnat.services.XnatAppInfo; import org.nrg.xnat.spawner.configuration.SpawnerConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -9,7 +10,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import org.springframework.context.support.ResourceBundleMessageSource; +import org.springframework.context.support.ReloadableResourceBundleMessageSource; import org.springframework.web.multipart.MultipartResolver; import org.springframework.web.multipart.support.StandardServletMultipartResolver; import org.springframework.web.servlet.ViewResolver; @@ -22,10 +23,13 @@ import org.springframework.web.servlet.view.JstlView; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; +import springfox.documentation.service.Contact; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; +import java.util.Locale; + @Configuration @EnableWebMvc @EnableSwagger2 @@ -59,19 +63,31 @@ public class WebConfig extends WebMvcConfigurerAdapter { @Bean public MessageSource messageSource() { - return new ResourceBundleMessageSource() {{ - setBasename("messages"); + return new ReloadableResourceBundleMessageSource() {{ + setBasename("classpath:org/nrg/xnat/messages/system"); }}; } @Bean - public Docket api() { + public Docket api(final XnatAppInfo info, final MessageSource messageSource) { _log.debug("Initializing the Swagger Docket object"); - return new Docket(DocumentationType.SWAGGER_2).select().apis(RequestHandlerSelectors.withClassAnnotation(XapiRestController.class)).paths(PathSelectors.any()).build().apiInfo(apiInfo()).pathMapping("/xapi"); + return new Docket(DocumentationType.SWAGGER_2).select().apis(RequestHandlerSelectors.withClassAnnotation(XapiRestController.class)).paths(PathSelectors.any()).build().apiInfo(apiInfo(info, messageSource)).pathMapping("/xapi"); + } + + private ApiInfo apiInfo(final XnatAppInfo info, final MessageSource messageSource) { + return new ApiInfo(getMessage(messageSource, "apiInfo.title"), + getMessage(messageSource, "apiInfo.description"), + info.getVersion(), + getMessage(messageSource, "apiInfo.termsOfServiceUrl"), + new Contact(getMessage(messageSource, "apiInfo.contactName"), + getMessage(messageSource, "apiInfo.contactUrl"), + getMessage(messageSource, "apiInfo.contactEmail")), + getMessage(messageSource, "apiInfo.license"), + getMessage(messageSource, "apiInfo.licenseUrl")); } - private ApiInfo apiInfo() { - return new ApiInfo("XNAT REST API", "The XNAT REST API (XAPI) functions provide access to XNAT internal functions for remote clients.", "1.7.0", "http://www.xnat.org", "info@xnat.org", "Simplified 2-Clause BSD", "API license URL"); + private String getMessage(final MessageSource messageSource, final String messageId) { + return messageSource.getMessage(messageId, null, Locale.getDefault()); } private static final Logger _log = LoggerFactory.getLogger(WebConfig.class); 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 838f5a495360a89161983cfb674a300f59a02ec1..08f84f6ee7eefe17af30e49e3094baa1211d660f 100644 --- a/src/main/java/org/nrg/xnat/event/listeners/AutoRunEmailHandler.java +++ b/src/main/java/org/nrg/xnat/event/listeners/AutoRunEmailHandler.java @@ -2,7 +2,7 @@ package org.nrg.xnat.event.listeners; import com.google.common.collect.Maps; -import org.nrg.xdat.XDAT; +import org.nrg.xdat.preferences.NotificationsPreferences; import reactor.bus.Event; import reactor.bus.EventBus; import reactor.fn.Consumer; @@ -24,50 +24,55 @@ import javax.inject.Inject; @Service public class AutoRunEmailHandler extends PipelineEmailHandlerAbst implements Consumer<Event<WorkflowStatusEvent>> { - /** The pipeline name. */ - private final String PIPELINE_NAME = "xnat_tools/AutoRun.xml"; - - /** The pipeline name pretty. */ - private final String PIPELINE_NAME_PRETTY = "AutoRun"; - - /** - * Instantiates a new auto run email handler. - * - * @param eventBus the event bus - */ - @Inject public AutoRunEmailHandler( EventBus eventBus ){ - eventBus.on(R(WorkflowStatusEvent.class.getName() + "[.](" - + PersistentWorkflowUtils.COMPLETE + "|" + PersistentWorkflowUtils.FAILED + ")"), 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); - } - - } + /** + * Instantiates a new auto run email handler. + * + * @param eventBus the event bus + * @param preferences The notifications preferences object. + */ + @Inject + public AutoRunEmailHandler(EventBus eventBus, final NotificationsPreferences preferences) { + _preferences = preferences; + eventBus.on(R(WorkflowStatusEvent.class.getName() + "[.](" + PersistentWorkflowUtils.COMPLETE + "|" + PersistentWorkflowUtils.FAILED + ")"), 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) */ public void handleEvent(WorkflowStatusEvent e) { - Map<String,Object> params = Maps.newHashMap(); - params.put("pipelineName",PIPELINE_NAME_PRETTY); - params.put("contactEmail", XDAT.getNotificationsPreferences().getHelpContactInfo()); + Map<String, Object> params = Maps.newHashMap(); + /* + The pipeline name pretty. + */ + final String PIPELINE_NAME_PRETTY = "AutoRun"; + params.put("pipelineName", PIPELINE_NAME_PRETTY); + params.put("contactEmail", _preferences.getHelpContactInfo()); if (!(e.getWorkflow() instanceof WrkWorkflowdata)) { - return; + return; } + /* + The pipeline name. + */ + final String PIPELINE_NAME = "xnat_tools/AutoRun.xml"; if (completed(e)) { - standardPipelineEmailImpl(e, (WrkWorkflowdata)e.getWorkflow(), PIPELINE_NAME, "/screens/PipelineEmail_AutoRun_success.vm", " archiving complete", "archival.lst", params); + standardPipelineEmailImpl(e, (WrkWorkflowdata) e.getWorkflow(), PIPELINE_NAME, "/screens/PipelineEmail_AutoRun_success.vm", " archiving complete", "archival.lst", params); } else if (failed(e)) { - standardPipelineEmailImpl(e, (WrkWorkflowdata)e.getWorkflow(), PIPELINE_NAME, DEFAULT_TEMPLATE_FAILURE, DEFAULT_SUBJECT_FAILURE, "archival.lst", params); + standardPipelineEmailImpl(e, (WrkWorkflowdata) e.getWorkflow(), PIPELINE_NAME, DEFAULT_TEMPLATE_FAILURE, DEFAULT_SUBJECT_FAILURE, "archival.lst", params); } } + private final NotificationsPreferences _preferences; } diff --git a/src/main/java/org/nrg/xnat/event/listeners/AutomationCompletionEventListener.java b/src/main/java/org/nrg/xnat/event/listeners/AutomationCompletionEventListener.java index ceb1ba91f75cba086c644f01a0e4d49d3478677f..98f8e587d3afd3811add263c0adbc5ba122b2a07 100644 --- a/src/main/java/org/nrg/xnat/event/listeners/AutomationCompletionEventListener.java +++ b/src/main/java/org/nrg/xnat/event/listeners/AutomationCompletionEventListener.java @@ -1,106 +1,90 @@ 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.automation.event.entities.AutomationCompletionEvent; -import org.nrg.xdat.XDAT; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; -import static reactor.bus.selector.Selectors.type; +import reactor.bus.Event; +import reactor.bus.EventBus; +import reactor.fn.Consumer; + +import javax.inject.Inject; import java.util.Iterator; import java.util.List; -import javax.inject.Inject; + +import static reactor.bus.selector.Selectors.type; /** * The Class AutomatedScriptHandler. */ @Service public class AutomationCompletionEventListener implements Consumer<Event<AutomationCompletionEvent>> { - - /** The Constant logger. */ + + /** + * 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(); - } - } - } + /** + * 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 automated script handler. + * + * @param eventBus the event bus + */ + @Inject + public AutomationCompletionEventListener(final EventBus eventBus) { + eventBus.on(type(AutomationCompletionEvent.class), this); + } + + @Override + public void accept(Event<AutomationCompletionEvent> event) { + cleanUpCache(); + if (logger.isDebugEnabled()) { + logger.debug("Received 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; - } + 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/DicomToNiftiEmailHandler.java b/src/main/java/org/nrg/xnat/event/listeners/DicomToNiftiEmailHandler.java index ed5dbdee71a0dd568c6e00884b08ebfa8e00a85a..d704a327c09651bb3ac488f0a1994a003a721024 100644 --- a/src/main/java/org/nrg/xnat/event/listeners/DicomToNiftiEmailHandler.java +++ b/src/main/java/org/nrg/xnat/event/listeners/DicomToNiftiEmailHandler.java @@ -1,47 +1,32 @@ package org.nrg.xnat.event.listeners; import com.google.common.collect.Maps; - -import org.nrg.xdat.XDAT; -import reactor.bus.Event; -import reactor.bus.EventBus; -import reactor.fn.Consumer; - -import org.apache.log4j.Logger; import org.nrg.xdat.om.WrkWorkflowdata; +import org.nrg.xdat.preferences.NotificationsPreferences; import org.nrg.xft.event.entities.WorkflowStatusEvent; import org.nrg.xft.event.persist.PersistentWorkflowUtils; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import reactor.bus.Event; +import reactor.bus.EventBus; +import reactor.fn.Consumer; -import static reactor.bus.selector.Selectors.R; - +import javax.inject.Inject; import java.util.Map; -import javax.inject.Inject; +import static reactor.bus.selector.Selectors.R; -/** - * Created by flavin on 3/2/15. - */ @Service public class DicomToNiftiEmailHandler extends PipelineEmailHandlerAbst implements Consumer<Event<WorkflowStatusEvent>> { - - /** The Constant logger. */ - final static Logger logger = Logger.getLogger(DicomToNiftiEmailHandler.class); - - /** The pipeline name. */ - private final String PIPELINE_NAME = "mricron/DicomToNifti.xml"; - - /** The pipeline name pretty. */ - private final String PIPELINE_NAME_PRETTY = "DicomToNifti"; - /** * Instantiates a new dicom to nifti email handler. - * - * @param eventBus the event bus + * @param eventBus the event bus + * @param preferences The site configuration preferences. */ - @Inject public DicomToNiftiEmailHandler( EventBus eventBus ){ - eventBus.on(R(WorkflowStatusEvent.class.getName() + "[.](" - + PersistentWorkflowUtils.COMPLETE + "|" + PersistentWorkflowUtils.FAILED + ")"), this); + @Autowired + public DicomToNiftiEmailHandler(EventBus eventBus, final NotificationsPreferences preferences){ + _preferences = preferences; + eventBus.on(R(WorkflowStatusEvent.class.getName() + "[.](" + PersistentWorkflowUtils.COMPLETE + "|" + PersistentWorkflowUtils.FAILED + ")"), this); } /* (non-Javadoc) @@ -49,12 +34,10 @@ public class DicomToNiftiEmailHandler extends PipelineEmailHandlerAbst implement */ @Override public void accept(Event<WorkflowStatusEvent> event) { - final WorkflowStatusEvent wfsEvent = event.getData(); if (wfsEvent.getWorkflow() instanceof WrkWorkflowdata) { handleEvent(wfsEvent); } - } /* (non-Javadoc) @@ -66,12 +49,18 @@ public class DicomToNiftiEmailHandler extends PipelineEmailHandlerAbst implement } WrkWorkflowdata wrk = (WrkWorkflowdata)e.getWorkflow(); Map<String,Object> params = Maps.newHashMap(); - params.put("pipelineName",PIPELINE_NAME_PRETTY); - params.put("contactEmail", XDAT.getNotificationsPreferences().getHelpContactInfo()); - if (completed(e)) { + /* The pipeline name pretty. */ + final String PIPELINE_NAME_PRETTY = "DicomToNifti"; + params.put("pipelineName", PIPELINE_NAME_PRETTY); + params.put("contactEmail", _preferences.getHelpContactInfo()); + /* The pipeline name. */ + final String PIPELINE_NAME = "mricron/DicomToNifti.xml"; + if (completed(e)) { standardPipelineEmailImpl(e, wrk, PIPELINE_NAME, DEFAULT_TEMPLATE_SUCCESS, DEFAULT_SUBJECT_SUCCESS, "processed.lst", params); } else if (failed(e)) { standardPipelineEmailImpl(e, wrk, PIPELINE_NAME, DEFAULT_TEMPLATE_FAILURE, DEFAULT_SUBJECT_FAILURE, "processed.lst", params); } } + + private final NotificationsPreferences _preferences; } diff --git a/src/main/java/org/nrg/xnat/event/listeners/methods/AbstractNotificationsPreferenceHandlerMethod.java b/src/main/java/org/nrg/xnat/event/listeners/methods/AbstractNotificationsPreferenceHandlerMethod.java index 5b2c2547e17b1330bc3c90f1a5e35d18f5b932ef..4fd8de2e31056078d1aa83507aeb654d007b5142 100644 --- a/src/main/java/org/nrg/xnat/event/listeners/methods/AbstractNotificationsPreferenceHandlerMethod.java +++ b/src/main/java/org/nrg/xnat/event/listeners/methods/AbstractNotificationsPreferenceHandlerMethod.java @@ -4,12 +4,12 @@ import org.nrg.prefs.events.PreferenceHandlerMethod; import org.nrg.xdat.preferences.NotificationsPreferences; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collections; import java.util.List; public abstract class AbstractNotificationsPreferenceHandlerMethod implements PreferenceHandlerMethod { @Override public List<String> getToolIds() { - return new ArrayList<String>(Arrays.asList(NotificationsPreferences.NOTIFICATIONS_TOOL_ID)); + return new ArrayList<>(Collections.singletonList(NotificationsPreferences.NOTIFICATIONS_TOOL_ID)); } } diff --git a/src/main/java/org/nrg/xnat/event/listeners/methods/AbstractSiteConfigNotificationsPreferenceHandlerMethod.java b/src/main/java/org/nrg/xnat/event/listeners/methods/AbstractSiteConfigNotificationsPreferenceHandlerMethod.java index f79800eea1813ad931dd43c43e842405eb62e520..588def3a0809f1075c486a5eb468f95e34e26e00 100644 --- a/src/main/java/org/nrg/xnat/event/listeners/methods/AbstractSiteConfigNotificationsPreferenceHandlerMethod.java +++ b/src/main/java/org/nrg/xnat/event/listeners/methods/AbstractSiteConfigNotificationsPreferenceHandlerMethod.java @@ -11,6 +11,6 @@ import java.util.List; public abstract class AbstractSiteConfigNotificationsPreferenceHandlerMethod implements PreferenceHandlerMethod { @Override public List<String> getToolIds() { - return new ArrayList<String>(Arrays.asList(NotificationsPreferences.NOTIFICATIONS_TOOL_ID, SiteConfigPreferences.SITE_CONFIG_TOOL_ID)); + return new ArrayList<>(Arrays.asList(NotificationsPreferences.NOTIFICATIONS_TOOL_ID, SiteConfigPreferences.SITE_CONFIG_TOOL_ID)); } } diff --git a/src/main/java/org/nrg/xnat/event/listeners/methods/AbstractSiteConfigPreferenceHandlerMethod.java b/src/main/java/org/nrg/xnat/event/listeners/methods/AbstractSiteConfigPreferenceHandlerMethod.java index 31dd51bcf200b611b9dd29210834fc651ef4e669..ca11bc01e0a03d449d98e57f040d27df5e0093ed 100644 --- a/src/main/java/org/nrg/xnat/event/listeners/methods/AbstractSiteConfigPreferenceHandlerMethod.java +++ b/src/main/java/org/nrg/xnat/event/listeners/methods/AbstractSiteConfigPreferenceHandlerMethod.java @@ -1,16 +1,35 @@ package org.nrg.xnat.event.listeners.methods; import org.nrg.prefs.events.PreferenceHandlerMethod; -import org.nrg.xdat.preferences.NotificationsPreferences; import org.nrg.xdat.preferences.SiteConfigPreferences; +import org.nrg.xft.security.UserI; +import org.nrg.xnat.utils.XnatUserProvider; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collections; import java.util.List; public abstract class AbstractSiteConfigPreferenceHandlerMethod implements PreferenceHandlerMethod { + protected AbstractSiteConfigPreferenceHandlerMethod() { + _userProvider = null; + } + + protected AbstractSiteConfigPreferenceHandlerMethod(final XnatUserProvider userProvider) { + _userProvider = userProvider; + } + @Override public List<String> getToolIds() { - return new ArrayList<String>(Arrays.asList(SiteConfigPreferences.SITE_CONFIG_TOOL_ID)); + return new ArrayList<>(Collections.singletonList(SiteConfigPreferences.SITE_CONFIG_TOOL_ID)); } + + protected UserI getAdminUser() { + return _userProvider != null ? _userProvider.get() : null; + } + + protected String getAdminUsername() { + return _userProvider != null ? _userProvider.getLogin() : ""; + } + + private final XnatUserProvider _userProvider; } diff --git a/src/main/java/org/nrg/xnat/event/listeners/methods/AliasTokenPreferenceHandlerMethod.java b/src/main/java/org/nrg/xnat/event/listeners/methods/AliasTokenPreferenceHandlerMethod.java index 56669628955fd28850927d1e15a9d81541273ebe..77436980b7b0ac55bdba9116f609622e9f51f997 100644 --- a/src/main/java/org/nrg/xnat/event/listeners/methods/AliasTokenPreferenceHandlerMethod.java +++ b/src/main/java/org/nrg/xnat/event/listeners/methods/AliasTokenPreferenceHandlerMethod.java @@ -1,13 +1,11 @@ package org.nrg.xnat.event.listeners.methods; import com.google.common.collect.ImmutableList; -import org.nrg.xdat.XDAT; +import org.nrg.xdat.preferences.SiteConfigPreferences; import org.nrg.xnat.security.alias.ClearExpiredAliasTokens; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Lazy; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.scheduling.support.CronTrigger; @@ -18,6 +16,13 @@ import java.util.concurrent.ScheduledFuture; @Component public class AliasTokenPreferenceHandlerMethod extends AbstractSiteConfigPreferenceHandlerMethod { + @Autowired + public AliasTokenPreferenceHandlerMethod(final SiteConfigPreferences preferences, final JdbcTemplate template, final ThreadPoolTaskScheduler scheduler) { + _preferences = preferences; + _template = template; + _scheduler = scheduler; + } + @Override public List<String> getHandledPreferences() { return PREFERENCES; @@ -44,7 +49,7 @@ public class AliasTokenPreferenceHandlerMethod extends AbstractSiteConfigPrefere future.cancel(false); } _timeouts.clear(); - _timeouts.add(_scheduler.schedule(new ClearExpiredAliasTokens(_template), new CronTrigger(XDAT.getSiteConfigPreferences().getAliasTokenTimeoutSchedule()))); + _timeouts.add(_scheduler.schedule(new ClearExpiredAliasTokens(_template), new CronTrigger(_preferences.getAliasTokenTimeoutSchedule()))); } catch (Exception e1) { _log.error("", e1); } @@ -53,13 +58,8 @@ public class AliasTokenPreferenceHandlerMethod extends AbstractSiteConfigPrefere private static final Logger _log = LoggerFactory.getLogger(AliasTokenPreferenceHandlerMethod.class); private static final List<String> PREFERENCES = ImmutableList.copyOf(Arrays.asList("aliasTokenTimeout", "aliasTokenTimeoutSchedule")); - @Autowired - @Lazy - private JdbcTemplate _template; - + private final SiteConfigPreferences _preferences; + private final JdbcTemplate _template; + private final ThreadPoolTaskScheduler _scheduler; private ArrayList<ScheduledFuture> _timeouts = new ArrayList<>(); - - @Autowired - @Qualifier("taskScheduler") - private ThreadPoolTaskScheduler _scheduler; } diff --git a/src/main/java/org/nrg/xnat/event/listeners/methods/AnonymizationHandlerMethod.java b/src/main/java/org/nrg/xnat/event/listeners/methods/AnonymizationHandlerMethod.java index 1729ff32f4adac7c199c6dc53f534d9263765ef6..eae6aa8fa17ada219465177511fb84e65ff57132 100644 --- a/src/main/java/org/nrg/xnat/event/listeners/methods/AnonymizationHandlerMethod.java +++ b/src/main/java/org/nrg/xnat/event/listeners/methods/AnonymizationHandlerMethod.java @@ -1,12 +1,10 @@ package org.nrg.xnat.event.listeners.methods; import com.google.common.collect.ImmutableList; -import org.nrg.xdat.XDAT; -import org.nrg.xdat.security.helpers.Users; -import org.nrg.xdat.security.services.RoleHolder; -import org.nrg.xft.security.UserI; +import org.nrg.xdat.preferences.SiteConfigPreferences; import org.nrg.xnat.helpers.editscript.DicomEdit; import org.nrg.xnat.helpers.merge.AnonUtils; +import org.nrg.xnat.utils.XnatUserProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -19,6 +17,12 @@ import java.util.Map; @Component public class AnonymizationHandlerMethod extends AbstractSiteConfigPreferenceHandlerMethod { + @Autowired + public AnonymizationHandlerMethod(final SiteConfigPreferences preferences, final XnatUserProvider primaryAdminUserProvider) { + super(primaryAdminUserProvider); + _preferences = preferences; + } + @Override public List<String> getHandledPreferences() { return PREFERENCES; @@ -33,42 +37,30 @@ public class AnonymizationHandlerMethod extends AbstractSiteConfigPreferenceHand @Override public void handlePreference(final String preference, final String value) { - if(PREFERENCES.contains(preference)){ + if (PREFERENCES.contains(preference)) { updateAnon(); } } - private void updateAnon(){ + private void updateAnon() { try { - AnonUtils.getService().setSiteWideScript(getAdminUser().getLogin(), DicomEdit.buildScriptPath(DicomEdit.ResourceScope.SITE_WIDE, null), XDAT.getSiteConfigPreferences().getSitewideAnonymizationScript()); - } - catch(Exception e){ - _log.error("Failed to set sitewide anon script.",e); + AnonUtils.getService().setSiteWideScript(getAdminUsername(), DicomEdit.buildScriptPath(DicomEdit.ResourceScope.SITE_WIDE, null), _preferences.getSitewideAnonymizationScript()); + } catch (Exception e) { + _log.error("Failed to set sitewide anon script.", e); } try { - if (XDAT.getSiteConfigPreferences().getEnableSitewideAnonymizationScript()) { - AnonUtils.getService().enableSiteWide(getAdminUser().getLogin(), DicomEdit.buildScriptPath(DicomEdit.ResourceScope.SITE_WIDE, null)); + if (_preferences.getEnableSitewideAnonymizationScript()) { + AnonUtils.getService().enableSiteWide(getAdminUsername(), DicomEdit.buildScriptPath(DicomEdit.ResourceScope.SITE_WIDE, null)); } else { - AnonUtils.getService().disableSiteWide(getAdminUser().getLogin(), DicomEdit.buildScriptPath(DicomEdit.ResourceScope.SITE_WIDE, null)); + AnonUtils.getService().disableSiteWide(getAdminUsername(), DicomEdit.buildScriptPath(DicomEdit.ResourceScope.SITE_WIDE, null)); } - } - catch(Exception e){ - _log.error("Failed to enable/disable sitewide anon script.",e); + } catch (Exception e) { + _log.error("Failed to enable/disable sitewide anon script.", e); } } private static final Logger _log = LoggerFactory.getLogger(AnonymizationHandlerMethod.class); private static final List<String> PREFERENCES = ImmutableList.copyOf(Arrays.asList("enableSitewideAnonymizationScript", "sitewideAnonymizationScript")); - private UserI getAdminUser() throws Exception { - for (String login : Users.getAllLogins()) { - final UserI user = Users.getUser(login); - if (_roleHolder.isSiteAdmin(user)) { - return user; - } - } - return null; - } - @Autowired - private RoleHolder _roleHolder; + private final SiteConfigPreferences _preferences; } diff --git a/src/main/java/org/nrg/xnat/event/listeners/methods/ChecksumsHandlerMethod.java b/src/main/java/org/nrg/xnat/event/listeners/methods/ChecksumsHandlerMethod.java index 4e4cd1647e5a62949376aa3ba23482e6bec708fc..ef0141d0d4f33b533e77aafabd339371c7ab2b17 100644 --- a/src/main/java/org/nrg/xnat/event/listeners/methods/ChecksumsHandlerMethod.java +++ b/src/main/java/org/nrg/xnat/event/listeners/methods/ChecksumsHandlerMethod.java @@ -1,19 +1,22 @@ package org.nrg.xnat.event.listeners.methods; import com.google.common.collect.ImmutableList; -import org.nrg.xdat.XDAT; +import org.nrg.xdat.preferences.SiteConfigPreferences; import org.nrg.xnat.utils.CatalogUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; @Component public class ChecksumsHandlerMethod extends AbstractSiteConfigPreferenceHandlerMethod { + @Autowired + public ChecksumsHandlerMethod(final SiteConfigPreferences preferences) { + _preferences = preferences; + } + @Override public List<String> getHandledPreferences() { return PREFERENCES; @@ -28,16 +31,16 @@ public class ChecksumsHandlerMethod extends AbstractSiteConfigPreferenceHandlerM @Override public void handlePreference(final String preference, final String value) { - if(PREFERENCES.contains(preference)){ + if (PREFERENCES.contains(preference)) { updateChecksums(); } } - private void updateChecksums(){ - CatalogUtils.setChecksumConfiguration(XDAT.getSiteConfigPreferences().getChecksums()); + private void updateChecksums() { + CatalogUtils.setChecksumConfiguration(_preferences.getChecksums()); } - private static final Logger _log = LoggerFactory.getLogger(ChecksumsHandlerMethod.class); - private static final List<String> PREFERENCES = ImmutableList.copyOf(Arrays.asList("checksums")); + private static final List<String> PREFERENCES = ImmutableList.copyOf(Collections.singletonList("checksums")); + private final SiteConfigPreferences _preferences; } diff --git a/src/main/java/org/nrg/xnat/event/listeners/methods/InactivityBeforeLockoutHandlerMethod.java b/src/main/java/org/nrg/xnat/event/listeners/methods/InactivityBeforeLockoutHandlerMethod.java index a4c2eae6d28ad1b72e59fd385a2a6858d79e40fe..df40d25a86d99afd47591858cc7aa520de0d03e4 100644 --- a/src/main/java/org/nrg/xnat/event/listeners/methods/InactivityBeforeLockoutHandlerMethod.java +++ b/src/main/java/org/nrg/xnat/event/listeners/methods/InactivityBeforeLockoutHandlerMethod.java @@ -1,22 +1,25 @@ package org.nrg.xnat.event.listeners.methods; import com.google.common.collect.ImmutableList; -import org.nrg.xdat.XDAT; import org.nrg.xdat.preferences.SiteConfigPreferences; import org.nrg.xnat.security.DisableInactiveUsers; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.scheduling.support.CronTrigger; import org.springframework.stereotype.Component; +import java.sql.SQLException; import java.util.*; import java.util.concurrent.ScheduledFuture; @Component public class InactivityBeforeLockoutHandlerMethod extends AbstractSiteConfigPreferenceHandlerMethod { + @Autowired + public InactivityBeforeLockoutHandlerMethod(final SiteConfigPreferences preferences, final ThreadPoolTaskScheduler scheduler) { + _preferences = preferences; + _scheduler = scheduler; + } + @Override public List<String> getHandledPreferences() { return PREFERENCES; @@ -31,33 +34,28 @@ public class InactivityBeforeLockoutHandlerMethod extends AbstractSiteConfigPref @Override public void handlePreference(final String preference, final String value) { - if(PREFERENCES.contains(preference)){ + if (PREFERENCES.contains(preference)) { updateInactivityBeforeLockout(); } } - private void updateInactivityBeforeLockout(){ - try { - _scheduler.getScheduledThreadPoolExecutor().setRemoveOnCancelPolicy(true); - - - for(ScheduledFuture temp: scheduledInactivityBeforeLockout){ - temp.cancel(false); - } - scheduledInactivityBeforeLockout.clear(); - scheduledInactivityBeforeLockout.add(_scheduler.schedule(new DisableInactiveUsers((new Long(SiteConfigPreferences.convertPGIntervalToSeconds(XDAT.getSiteConfigPreferences().getInactivityBeforeLockout()))).intValue(),(new Long(SiteConfigPreferences.convertPGIntervalToSeconds(XDAT.getSiteConfigPreferences().getMaxFailedLoginsLockoutDuration()))).intValue()),new CronTrigger(XDAT.getSiteConfigPreferences().getInactivityBeforeLockoutSchedule()))); - - } catch (Exception e1) { - _log.error("", e1); - } - } + private void updateInactivityBeforeLockout() { + _scheduler.getScheduledThreadPoolExecutor().setRemoveOnCancelPolicy(true); + for (ScheduledFuture temp : _scheduledInactivityBeforeLockout) { + temp.cancel(false); + } + _scheduledInactivityBeforeLockout.clear(); + try { + _scheduledInactivityBeforeLockout.add(_scheduler.schedule(new DisableInactiveUsers((new Long(SiteConfigPreferences.convertPGIntervalToSeconds(_preferences.getInactivityBeforeLockout()))).intValue(), (new Long(SiteConfigPreferences.convertPGIntervalToSeconds(_preferences.getMaxFailedLoginsLockoutDuration()))).intValue()), new CronTrigger(_preferences.getInactivityBeforeLockoutSchedule()))); + } catch (SQLException ignored) { + // Do nothing: the SQL exception here is superfluous. + } + } - private static final Logger _log = LoggerFactory.getLogger(InactivityBeforeLockoutHandlerMethod.class); private static final List<String> PREFERENCES = ImmutableList.copyOf(Arrays.asList("inactivityBeforeLockout", "inactivityBeforeLockoutSchedule")); - private ArrayList<ScheduledFuture> scheduledInactivityBeforeLockout = new ArrayList<>(); + private final ArrayList<ScheduledFuture> _scheduledInactivityBeforeLockout = new ArrayList<>(); - @Autowired - @Qualifier("taskScheduler") - private ThreadPoolTaskScheduler _scheduler; + private final SiteConfigPreferences _preferences; + private final ThreadPoolTaskScheduler _scheduler; } diff --git a/src/main/java/org/nrg/xnat/event/listeners/methods/MailHandlerMethod.java b/src/main/java/org/nrg/xnat/event/listeners/methods/MailHandlerMethod.java index c42579dd7a639f4634b20cbc70409db0797a3e84..95d70d9282944036569c595d8389e2f4d304e992 100644 --- a/src/main/java/org/nrg/xnat/event/listeners/methods/MailHandlerMethod.java +++ b/src/main/java/org/nrg/xnat/event/listeners/methods/MailHandlerMethod.java @@ -3,12 +3,13 @@ package org.nrg.xnat.event.listeners.methods; import com.google.common.collect.ImmutableList; import org.nrg.notify.renderers.ChannelRenderer; import org.nrg.notify.renderers.NrgMailChannelRenderer; -import org.nrg.xdat.XDAT; +import org.nrg.xdat.preferences.NotificationsPreferences; +import org.nrg.xdat.preferences.SiteConfigPreferences; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import javax.inject.Inject; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -16,6 +17,13 @@ import java.util.Map; @Component public class MailHandlerMethod extends AbstractSiteConfigNotificationsPreferenceHandlerMethod { + @Autowired + public MailHandlerMethod(final SiteConfigPreferences siteConfigPreferences, final NotificationsPreferences notificationsPreferences, final ChannelRenderer mailRenderer) { + _siteConfigPreferences = siteConfigPreferences; + _notificationsPreferences = notificationsPreferences; + _mailRenderer = mailRenderer; + } + @Override public List<String> getHandledPreferences() { return PREFERENCES; @@ -37,9 +45,8 @@ public class MailHandlerMethod extends AbstractSiteConfigNotificationsPreference private void updateMail(){ try { - ((NrgMailChannelRenderer)_mailRenderer).setFromAddress(XDAT.getSiteConfigPreferences().getAdminEmail()); - ((NrgMailChannelRenderer)_mailRenderer).setSubjectPrefix(XDAT.getNotificationsPreferences().getEmailPrefix()); - + ((NrgMailChannelRenderer)_mailRenderer).setFromAddress(_siteConfigPreferences.getAdminEmail()); + ((NrgMailChannelRenderer)_mailRenderer).setSubjectPrefix(_notificationsPreferences.getEmailPrefix()); } catch (Exception e1) { _log.error("", e1); } @@ -48,7 +55,7 @@ public class MailHandlerMethod extends AbstractSiteConfigNotificationsPreference private static final Logger _log = LoggerFactory.getLogger(MailHandlerMethod.class); private static final List<String> PREFERENCES = ImmutableList.copyOf(Arrays.asList("emailPrefix", "adminEmail")); - @Inject - private ChannelRenderer _mailRenderer; - + private final SiteConfigPreferences _siteConfigPreferences; + private final NotificationsPreferences _notificationsPreferences; + private final ChannelRenderer _mailRenderer; } diff --git a/src/main/java/org/nrg/xnat/event/listeners/methods/PasswordExpirationHandlerMethod.java b/src/main/java/org/nrg/xnat/event/listeners/methods/PasswordExpirationHandlerMethod.java index baa4ac3d3a4418d837117ef3aafdb2cf3f210005..090ea9de12b966fd73715e1baa7505bd85ba7069 100644 --- a/src/main/java/org/nrg/xnat/event/listeners/methods/PasswordExpirationHandlerMethod.java +++ b/src/main/java/org/nrg/xnat/event/listeners/methods/PasswordExpirationHandlerMethod.java @@ -5,7 +5,6 @@ import org.nrg.xnat.security.XnatExpiredPasswordFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import java.util.Arrays; @@ -15,6 +14,11 @@ import java.util.Map; @Component public class PasswordExpirationHandlerMethod extends AbstractSiteConfigPreferenceHandlerMethod { + @Autowired + public PasswordExpirationHandlerMethod(final XnatExpiredPasswordFilter filter) { + _filter = filter; + } + @Override public List<String> getHandledPreferences() { return PREFERENCES; @@ -41,7 +45,5 @@ public class PasswordExpirationHandlerMethod extends AbstractSiteConfigPreferenc private static final Logger _log = LoggerFactory.getLogger(PasswordExpirationHandlerMethod.class); private static final List<String> PREFERENCES = ImmutableList.copyOf(Arrays.asList("passwordExpirationType", "passwordExpirationInterval", "passwordExpirationDate")); - @Autowired - @Qualifier("expiredPasswordFilter") - private XnatExpiredPasswordFilter _filter; + private final XnatExpiredPasswordFilter _filter; } diff --git a/src/main/java/org/nrg/xnat/event/listeners/methods/PathHandlerMethod.java b/src/main/java/org/nrg/xnat/event/listeners/methods/PathHandlerMethod.java index 933144ddf0cf25f81506bae0724c0cae88766e7c..55fde87ce670c203b66b539f0abcd3ddd1a7d267 100644 --- a/src/main/java/org/nrg/xnat/event/listeners/methods/PathHandlerMethod.java +++ b/src/main/java/org/nrg/xnat/event/listeners/methods/PathHandlerMethod.java @@ -1,10 +1,14 @@ package org.nrg.xnat.event.listeners.methods; import com.google.common.collect.ImmutableList; -import org.nrg.xdat.security.helpers.Users; -import org.nrg.xdat.security.services.RoleHolder; +import org.nrg.framework.exceptions.NrgServiceError; +import org.nrg.framework.exceptions.NrgServiceRuntimeException; +import org.nrg.xdat.security.user.exceptions.UserInitException; +import org.nrg.xft.exception.ElementNotFoundException; import org.nrg.xft.security.UserI; +import org.nrg.xnat.services.XnatAppInfo; import org.nrg.xnat.turbine.utils.ArcSpecManager; +import org.nrg.xnat.utils.XnatUserProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -17,6 +21,12 @@ import java.util.Map; @Component public class PathHandlerMethod extends AbstractSiteConfigPreferenceHandlerMethod { + @Autowired + public PathHandlerMethod(final XnatUserProvider primaryAdminUserProvider, final XnatAppInfo appInfo) { + super(primaryAdminUserProvider); + _appInfo = appInfo; + } + @Override public List<String> getHandledPreferences() { return PREFERENCES; @@ -37,31 +47,31 @@ public class PathHandlerMethod extends AbstractSiteConfigPreferenceHandlerMethod } private void updateArchivePath() { - try { - ArcSpecManager.initialize(getAdminUser()); - } catch (Exception e) { - _log.error("",e); + if (!_appInfo.isInitialized()) { + _log.warn("Application is not yet initialized, update archive path operation aborted."); + return; } - } - private UserI getAdminUser() { - for (String login : Users.getAllLogins()) { - try { - final UserI user = Users.getUser(login); - if (_roleHolder.isSiteAdmin(user)) { - return user; - } + try { + final UserI adminUser = getAdminUser(); + if (adminUser == null) { + _log.error("No error occurred but failed to retrieve admin user, can't proceed with archive path update."); + } else { + ArcSpecManager.initialize(adminUser); } - catch(Exception e){ - + } catch (final ElementNotFoundException | UserInitException | NrgServiceRuntimeException e) { + if (!(e instanceof NrgServiceRuntimeException) || ((NrgServiceRuntimeException) e).getServiceError() == NrgServiceError.UserServiceError) { + _log.warn("The user for initializing the arcspec could not be initialized. This probably means the system is still initializing. Check the database if this is not the case."); + } else { + _log.error("An unknown error occurred trying to update the archive path.", e); } + } catch (final Exception e) { + _log.error("An unknown error occurred trying to update the archive path.", e); } - return null; } private static final Logger _log = LoggerFactory.getLogger(PathHandlerMethod.class); - private static final List<String> PREFERENCES = ImmutableList.copyOf(Arrays.asList("archivePath","prearchivePath","cachePath","ftpPath","buildPath","pipelinePath")); + private static final List<String> PREFERENCES = ImmutableList.copyOf(Arrays.asList("archivePath", "prearchivePath", "cachePath", "ftpPath", "buildPath", "pipelinePath")); - @Autowired - private RoleHolder _roleHolder; + private final XnatAppInfo _appInfo; } diff --git a/src/main/java/org/nrg/xnat/event/listeners/methods/PetTracerHandlerMethod.java b/src/main/java/org/nrg/xnat/event/listeners/methods/PetTracerHandlerMethod.java index 6e2c5d232ce0c9b49b95b74ea7204cafd30a7d47..e5e8f0a28efa93c481316e40e9f5d209b47842e4 100644 --- a/src/main/java/org/nrg/xnat/event/listeners/methods/PetTracerHandlerMethod.java +++ b/src/main/java/org/nrg/xnat/event/listeners/methods/PetTracerHandlerMethod.java @@ -1,23 +1,27 @@ package org.nrg.xnat.event.listeners.methods; import com.google.common.collect.ImmutableList; -import org.nrg.xdat.XDAT; -import org.nrg.xdat.security.helpers.Users; -import org.nrg.xdat.security.services.RoleHolder; -import org.nrg.xft.security.UserI; +import org.nrg.xdat.preferences.SiteConfigPreferences; import org.nrg.xnat.services.PETTracerUtils; +import org.nrg.xnat.utils.XnatUserProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; @Component public class PetTracerHandlerMethod extends AbstractSiteConfigPreferenceHandlerMethod { + @Autowired + public PetTracerHandlerMethod(final SiteConfigPreferences preferences, final XnatUserProvider primaryAdminUserProvider, final PETTracerUtils petTracerUtils) { + super(primaryAdminUserProvider); + _preferences = preferences; + _petTracerUtils = petTracerUtils; + } + @Override public List<String> getHandledPreferences() { return PREFERENCES; @@ -32,32 +36,22 @@ public class PetTracerHandlerMethod extends AbstractSiteConfigPreferenceHandlerM @Override public void handlePreference(final String preference, final String value) { - if(PREFERENCES.contains(preference)){ + if (PREFERENCES.contains(preference)) { updatePetTracer(); } } - private void updatePetTracer(){ + private void updatePetTracer() { try { - PETTracerUtils.getService().setSiteWideTracerList(getAdminUser().getLogin(), PETTracerUtils.buildScriptPath(PETTracerUtils.ResourceScope.SITE_WIDE, ""), XDAT.getSiteConfigPreferences().getSitewidePetTracers()); - } - catch(Exception e){ - _log.error("Failed to set sitewide anon script.",e); + _petTracerUtils.setSiteWideTracerList(getAdminUsername(), PETTracerUtils.buildScriptPath(PETTracerUtils.ResourceScope.SITE_WIDE, ""), _preferences.getSitewidePetTracers()); + } catch (Exception e) { + _log.error("Failed to set sitewide anon script.", e); } } private static final Logger _log = LoggerFactory.getLogger(PetTracerHandlerMethod.class); - private static final List<String> PREFERENCES = ImmutableList.copyOf(Arrays.asList("sitewidePetTracers")); - - private UserI getAdminUser() throws Exception { - for (String login : Users.getAllLogins()) { - final UserI user = Users.getUser(login); - if (_roleHolder.isSiteAdmin(user)) { - return user; - } - } - return null; - } - @Autowired - private RoleHolder _roleHolder; + private static final List<String> PREFERENCES = ImmutableList.copyOf(Collections.singletonList("sitewidePetTracers")); + + private final SiteConfigPreferences _preferences; + private final PETTracerUtils _petTracerUtils; } diff --git a/src/main/java/org/nrg/xnat/event/listeners/methods/RequiredChannelHandlerMethod.java b/src/main/java/org/nrg/xnat/event/listeners/methods/RequiredChannelHandlerMethod.java index b21487cf645575628e938587a08b69ec4bb98efe..3aee25ec083c03961d876e3a18ba18b7ac445d9f 100644 --- a/src/main/java/org/nrg/xnat/event/listeners/methods/RequiredChannelHandlerMethod.java +++ b/src/main/java/org/nrg/xnat/event/listeners/methods/RequiredChannelHandlerMethod.java @@ -1,21 +1,23 @@ package org.nrg.xnat.event.listeners.methods; import com.google.common.collect.ImmutableList; -import org.nrg.xdat.XDAT; +import org.nrg.xdat.preferences.SiteConfigPreferences; import org.nrg.xnat.security.TranslatingChannelProcessingFilter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; @Component public class RequiredChannelHandlerMethod extends AbstractSiteConfigPreferenceHandlerMethod { + @Autowired + public RequiredChannelHandlerMethod(final SiteConfigPreferences preferences, final TranslatingChannelProcessingFilter filter) { + _preferences = preferences; + _filter = filter; + } + @Override public List<String> getHandledPreferences() { return PREFERENCES; @@ -30,19 +32,17 @@ public class RequiredChannelHandlerMethod extends AbstractSiteConfigPreferenceHa @Override public void handlePreference(final String preference, final String value) { - if(PREFERENCES.contains(preference)){ + if (PREFERENCES.contains(preference)) { updateRequiredChannel(); } } - private void updateRequiredChannel(){ - _filter.setRequiredChannel(XDAT.getSiteConfigPreferences().getSecurityChannel()); - } + private void updateRequiredChannel() { + _filter.setRequiredChannel(_preferences.getSecurityChannel()); + } - private static final Logger _log = LoggerFactory.getLogger(RequiredChannelHandlerMethod.class); - private static final List<String> PREFERENCES = ImmutableList.copyOf(Arrays.asList("security.channel")); + private static final List<String> PREFERENCES = ImmutableList.copyOf(Collections.singletonList("security.channel")); - @Autowired - @Qualifier("channelProcessingFilter") - private TranslatingChannelProcessingFilter _filter; + private final SiteConfigPreferences _preferences; + private final TranslatingChannelProcessingFilter _filter; } diff --git a/src/main/java/org/nrg/xnat/event/listeners/methods/ResetFailedLoginsHandlerMethod.java b/src/main/java/org/nrg/xnat/event/listeners/methods/ResetFailedLoginsHandlerMethod.java index fd94e3146ff6068c9583bee3ec033cec416cf0a9..9f9e50681b256eef9b25d2931df858f668b6b811 100644 --- a/src/main/java/org/nrg/xnat/event/listeners/methods/ResetFailedLoginsHandlerMethod.java +++ b/src/main/java/org/nrg/xnat/event/listeners/methods/ResetFailedLoginsHandlerMethod.java @@ -1,13 +1,9 @@ package org.nrg.xnat.event.listeners.methods; import com.google.common.collect.ImmutableList; -import org.nrg.xdat.XDAT; +import org.nrg.xdat.preferences.SiteConfigPreferences; import org.nrg.xnat.security.ResetFailedLogins; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Lazy; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.scheduling.support.CronTrigger; @@ -18,6 +14,13 @@ import java.util.concurrent.ScheduledFuture; @Component public class ResetFailedLoginsHandlerMethod extends AbstractSiteConfigPreferenceHandlerMethod { + @Autowired + public ResetFailedLoginsHandlerMethod(final SiteConfigPreferences preferences, final JdbcTemplate template, final ThreadPoolTaskScheduler scheduler) { + _preferences = preferences; + _template = template; + _scheduler = scheduler; + } + @Override public List<String> getHandledPreferences() { return PREFERENCES; @@ -32,37 +35,26 @@ public class ResetFailedLoginsHandlerMethod extends AbstractSiteConfigPreference @Override public void handlePreference(final String preference, final String value) { - if(PREFERENCES.contains(preference)){ + if (PREFERENCES.contains(preference)) { updateResetFailedLogins(); } } - private void updateResetFailedLogins(){ - try { - _scheduler.getScheduledThreadPoolExecutor().setRemoveOnCancelPolicy(true); - _scheduler.getScheduledThreadPoolExecutor().getQueue().iterator(); - - for(ScheduledFuture temp: scheduledResetFailedLogins){ - temp.cancel(false); - } - scheduledResetFailedLogins.clear(); - scheduledResetFailedLogins.add(_scheduler.schedule(new ResetFailedLogins(_template,XDAT.getSiteConfigPreferences().getMaxFailedLoginsLockoutDuration()),new CronTrigger(XDAT.getSiteConfigPreferences().getResetFailedLoginsSchedule()))); - - } catch (Exception e1) { - _log.error("", e1); - } - } + private void updateResetFailedLogins() { + _scheduler.getScheduledThreadPoolExecutor().setRemoveOnCancelPolicy(true); + _scheduler.getScheduledThreadPoolExecutor().getQueue().iterator(); + for (ScheduledFuture temp : scheduledResetFailedLogins) { + temp.cancel(false); + } + scheduledResetFailedLogins.clear(); + scheduledResetFailedLogins.add(_scheduler.schedule(new ResetFailedLogins(_template, _preferences.getMaxFailedLoginsLockoutDuration()), new CronTrigger(_preferences.getResetFailedLoginsSchedule()))); + } - private static final Logger _log = LoggerFactory.getLogger(ResetFailedLoginsHandlerMethod.class); private static final List<String> PREFERENCES = ImmutableList.copyOf(Arrays.asList("maxFailedLoginsLockoutDuration", "resetFailedLoginsSchedule")); - @Autowired - @Lazy - private JdbcTemplate _template; + private final ArrayList<ScheduledFuture> scheduledResetFailedLogins = new ArrayList<>(); - private ArrayList<ScheduledFuture> scheduledResetFailedLogins = new ArrayList<>(); - - @Autowired - @Qualifier("taskScheduler") - private ThreadPoolTaskScheduler _scheduler; + private final SiteConfigPreferences _preferences; + private final JdbcTemplate _template; + private final ThreadPoolTaskScheduler _scheduler; } diff --git a/src/main/java/org/nrg/xnat/event/listeners/methods/RoleServicesHandlerMethod.java b/src/main/java/org/nrg/xnat/event/listeners/methods/RoleServicesHandlerMethod.java index a1072e3dbd7650dfa72bd1e9cb91cd968e9d2338..bdfdda227a053b651e9aebfe1202d4f98450b974 100644 --- a/src/main/java/org/nrg/xnat/event/listeners/methods/RoleServicesHandlerMethod.java +++ b/src/main/java/org/nrg/xnat/event/listeners/methods/RoleServicesHandlerMethod.java @@ -1,7 +1,7 @@ package org.nrg.xnat.event.listeners.methods; import com.google.common.collect.ImmutableList; -import org.nrg.xdat.XDAT; +import org.nrg.xdat.preferences.SiteConfigPreferences; import org.nrg.xdat.security.services.RoleHolder; import org.nrg.xdat.security.services.RoleRepositoryHolder; import org.nrg.xdat.security.services.RoleRepositoryServiceI; @@ -18,6 +18,13 @@ import java.util.Map; @Component public class RoleServicesHandlerMethod extends AbstractSiteConfigPreferenceHandlerMethod { + @Autowired + public RoleServicesHandlerMethod(final SiteConfigPreferences preferences, final RoleHolder roleHolder, final RoleRepositoryHolder roleRepositoryHolder) { + _preferences = preferences; + _roleHolder = roleHolder; + _roleRepositoryHolder = roleRepositoryHolder; + } + @Override public List<String> getHandledPreferences() { return PREFERENCES; @@ -32,34 +39,39 @@ public class RoleServicesHandlerMethod extends AbstractSiteConfigPreferenceHandl @Override public void handlePreference(final String preference, final String value) { - if(PREFERENCES.contains(preference)){ + if (PREFERENCES.contains(preference)) { updateFeatureServices(); } } - private void updateFeatureServices(){ + private void updateFeatureServices() { + final String roleService = _preferences.getRoleService(); + final String roleRepositoryService = _preferences.getRoleRepositoryService(); try { - _roleHolder.setRoleService(Class.forName(XDAT.getSiteConfigPreferences().getRoleService()).asSubclass(RoleServiceI.class).newInstance()); - } - catch(Exception e){ - _log.error("",e); + _roleHolder.setRoleService(Class.forName(roleService).asSubclass(RoleServiceI.class).newInstance()); + } catch (InstantiationException e) { + _log.error("An error occurred creating the role service with class: " + roleService, e); + } catch (IllegalAccessException e) { + _log.error("Access denied when creating the role service with class: " + roleService, e); + } catch (ClassNotFoundException e) { + _log.error("Could not find the specified role service class on the classpath: " + roleService, e); } try { - _roleRepositoryHolder.setRoleRepositoryService(Class.forName(XDAT.getSiteConfigPreferences().getRoleRepositoryService()).asSubclass(RoleRepositoryServiceI.class).newInstance()); - } - catch(Exception e){ - _log.error("",e); + _roleRepositoryHolder.setRoleRepositoryService(Class.forName(roleRepositoryService).asSubclass(RoleRepositoryServiceI.class).newInstance()); + } catch (InstantiationException e) { + _log.error("An error occurred creating the role repository service with class: " + roleRepositoryService, e); + } catch (IllegalAccessException e) { + _log.error("Access denied when creating the role repository service with class: " + roleRepositoryService, e); + } catch (ClassNotFoundException e) { + _log.error("Could not find the specified role repository service class on the classpath: " + roleRepositoryService, e); } } private static final Logger _log = LoggerFactory.getLogger(RoleServicesHandlerMethod.class); private static final List<String> PREFERENCES = ImmutableList.copyOf(Arrays.asList("security.services.role.default", "security.services.roleRepository.default")); - - @Autowired - private RoleHolder _roleHolder; - - @Autowired - private RoleRepositoryHolder _roleRepositoryHolder; + private final SiteConfigPreferences _preferences; + private final RoleHolder _roleHolder; + private final RoleRepositoryHolder _roleRepositoryHolder; } diff --git a/src/main/java/org/nrg/xnat/event/listeners/methods/SeriesImportFilterHandlerMethod.java b/src/main/java/org/nrg/xnat/event/listeners/methods/SeriesImportFilterHandlerMethod.java index 9d5f3e2e6cebb571fffe9d56bbc1ee16090defe5..da70c241cc3df74a0c059f752e17b816121ba046 100644 --- a/src/main/java/org/nrg/xnat/event/listeners/methods/SeriesImportFilterHandlerMethod.java +++ b/src/main/java/org/nrg/xnat/event/listeners/methods/SeriesImportFilterHandlerMethod.java @@ -2,14 +2,11 @@ package org.nrg.xnat.event.listeners.methods; import com.google.common.collect.ImmutableList; import org.nrg.dicomtools.filters.*; -import org.nrg.xdat.XDAT; -import org.nrg.xdat.security.helpers.Users; -import org.nrg.xdat.security.services.RoleHolder; -import org.nrg.xft.security.UserI; +import org.nrg.xdat.preferences.SiteConfigPreferences; +import org.nrg.xnat.utils.XnatUserProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import java.util.Arrays; @@ -19,6 +16,13 @@ import java.util.Map; @Component public class SeriesImportFilterHandlerMethod extends AbstractSiteConfigPreferenceHandlerMethod { + @Autowired + public SeriesImportFilterHandlerMethod(final SiteConfigPreferences preferences, final DicomFilterService dicomFilterService, final XnatUserProvider primaryAdminUserProvider) { + super(primaryAdminUserProvider); + _preferences = preferences; + _dicomFilterService = dicomFilterService; + } + @Override public List<String> getHandledPreferences() { return PREFERENCES; @@ -33,58 +37,34 @@ public class SeriesImportFilterHandlerMethod extends AbstractSiteConfigPreferenc @Override public void handlePreference(final String preference, final String value) { - if(PREFERENCES.contains(preference)){ + if (PREFERENCES.contains(preference)) { updateSeriesImportFilter(); } } - private void updateSeriesImportFilter(){ + private void updateSeriesImportFilter() { try { - final boolean enabled = XDAT.getSiteConfigPreferences().getEnableSitewideSeriesImportFilter(); - final SeriesImportFilterMode mode = SeriesImportFilterMode.mode(XDAT.getSiteConfigPreferences().getSitewideSeriesImportFilterMode()); - final String filterContents = XDAT.getSiteConfigPreferences().getSitewideSeriesImportFilter(); + final boolean enabled = _preferences.getEnableSitewideSeriesImportFilter(); + final SeriesImportFilterMode mode = SeriesImportFilterMode.mode(_preferences.getSitewideSeriesImportFilterMode()); + final String filterContents = _preferences.getSitewideSeriesImportFilter(); final SeriesImportFilter seriesImportFilter; if (mode == SeriesImportFilterMode.ModalityMap) { seriesImportFilter = new ModalityMapSeriesImportFilter(filterContents, enabled); } else { seriesImportFilter = new RegExBasedSeriesImportFilter(filterContents, mode, enabled); } - if (!seriesImportFilter.equals(getSeriesImportFilter())) { - getDicomFilterService().commit(seriesImportFilter, getAdminUser().getLogin(), "Updated site-wide series import filter from administrator UI."); + final SeriesImportFilter sitewide = _dicomFilterService.getSeriesImportFilter(); + if (!seriesImportFilter.equals(sitewide)) { + _dicomFilterService.commit(seriesImportFilter, getAdminUsername(), "Updated site-wide series import filter from administrator UI."); } - } - catch(Exception e){ - _log.error("Failed to update Series Import Filter.",e); + } catch (Exception e) { + _log.error("Failed to update Series Import Filter.", e); } } private static final Logger _log = LoggerFactory.getLogger(SeriesImportFilterHandlerMethod.class); private static final List<String> PREFERENCES = ImmutableList.copyOf(Arrays.asList("enableSitewideSeriesImportFilter", "sitewideSeriesImportFilterMode", "sitewideSeriesImportFilter")); - private UserI getAdminUser() throws Exception { - for (String login : Users.getAllLogins()) { - final UserI user = Users.getUser(login); - if (_roleHolder.isSiteAdmin(user)) { - return user; - } - } - return null; - } - - private DicomFilterService getDicomFilterService() { - return XDAT.getContextService().getBean(DicomFilterService.class); - } - - private SeriesImportFilter getSeriesImportFilter() { - DicomFilterService service = getDicomFilterService(); - if (service != null) { - return service.getSeriesImportFilter(); - } - else{ - return null; - } - } - - @Autowired - private RoleHolder _roleHolder; + private final SiteConfigPreferences _preferences; + private final DicomFilterService _dicomFilterService; } diff --git a/src/main/java/org/nrg/xnat/event/listeners/methods/SessionXmlRebuilderHandlerMethod.java b/src/main/java/org/nrg/xnat/event/listeners/methods/SessionXmlRebuilderHandlerMethod.java index b6b42b7fa088102489b668e11090e8c172ec049e..037a2cedcf6bffbbe1f43931539e95a0fd202b5f 100644 --- a/src/main/java/org/nrg/xnat/event/listeners/methods/SessionXmlRebuilderHandlerMethod.java +++ b/src/main/java/org/nrg/xnat/event/listeners/methods/SessionXmlRebuilderHandlerMethod.java @@ -1,24 +1,30 @@ package org.nrg.xnat.event.listeners.methods; import com.google.common.collect.ImmutableList; -import org.nrg.xdat.XDAT; +import org.nrg.xdat.preferences.SiteConfigPreferences; import org.nrg.xnat.helpers.prearchive.SessionXMLRebuilder; +import org.nrg.xnat.services.XnatAppInfo; import org.nrg.xnat.utils.XnatUserProvider; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.jms.core.JmsTemplate; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.scheduling.support.PeriodicTrigger; import org.springframework.stereotype.Component; -import javax.inject.Inject; import java.util.*; import java.util.concurrent.ScheduledFuture; @Component public class SessionXmlRebuilderHandlerMethod extends AbstractSiteConfigPreferenceHandlerMethod { + @Autowired + public SessionXmlRebuilderHandlerMethod(final SiteConfigPreferences preferences, final ThreadPoolTaskScheduler scheduler, final JmsTemplate jmsTemplate, final XnatUserProvider primaryAdminUserProvider, final XnatAppInfo appInfo) { + _preferences = preferences; + _scheduler = scheduler; + _jmsTemplate = jmsTemplate; + _provider = primaryAdminUserProvider; + _appInfo = appInfo; + } + @Override public List<String> getHandledPreferences() { return PREFERENCES; @@ -33,38 +39,27 @@ public class SessionXmlRebuilderHandlerMethod extends AbstractSiteConfigPreferen @Override public void handlePreference(final String preference, final String value) { - if(PREFERENCES.contains(preference)){ + if (PREFERENCES.contains(preference)) { updateSessionXmlRebuilder(); } } - private void updateSessionXmlRebuilder(){ - try { - _scheduler.getScheduledThreadPoolExecutor().setRemoveOnCancelPolicy(true); - - for(ScheduledFuture temp: scheduledXmlRebuilder){ - temp.cancel(false); - } - scheduledXmlRebuilder.clear(); - scheduledXmlRebuilder.add(_scheduler.schedule(new SessionXMLRebuilder(_provider, XDAT.getSiteConfigPreferences().getSessionXmlRebuilderInterval(), _jmsTemplate),new PeriodicTrigger(XDAT.getSiteConfigPreferences().getSessionXmlRebuilderRepeat()))); - - } catch (Exception e1) { - _log.error("", e1); - } - } + private void updateSessionXmlRebuilder() { + _scheduler.getScheduledThreadPoolExecutor().setRemoveOnCancelPolicy(true); + for (ScheduledFuture temp : scheduledXmlRebuilder) { + temp.cancel(false); + } + scheduledXmlRebuilder.clear(); + scheduledXmlRebuilder.add(_scheduler.schedule(new SessionXMLRebuilder(_provider, _appInfo, _jmsTemplate, _preferences.getSessionXmlRebuilderInterval()), new PeriodicTrigger(_preferences.getSessionXmlRebuilderRepeat()))); + } - private static final Logger _log = LoggerFactory.getLogger(SessionXmlRebuilderHandlerMethod.class); private static final List<String> PREFERENCES = ImmutableList.copyOf(Arrays.asList("sessionXmlRebuilderRepeat", "sessionXmlRebuilderInterval")); - private ArrayList<ScheduledFuture> scheduledXmlRebuilder = new ArrayList<>(); - - @Autowired - @Qualifier("taskScheduler") - private ThreadPoolTaskScheduler _scheduler; - - @Inject - private JmsTemplate _jmsTemplate; + private final ArrayList<ScheduledFuture> scheduledXmlRebuilder = new ArrayList<>(); - @Inject - private XnatUserProvider _provider; + private final SiteConfigPreferences _preferences; + private final ThreadPoolTaskScheduler _scheduler; + private final JmsTemplate _jmsTemplate; + private final XnatUserProvider _provider; + private XnatAppInfo _appInfo; } diff --git a/src/main/java/org/nrg/xnat/event/listeners/methods/SmtpHandlerMethod.java b/src/main/java/org/nrg/xnat/event/listeners/methods/SmtpHandlerMethod.java index 7b9a8bb304f44859c7169e81c377dd7437d80c19..ceaa07a8618622b281fd8f28393d55705d339058 100644 --- a/src/main/java/org/nrg/xnat/event/listeners/methods/SmtpHandlerMethod.java +++ b/src/main/java/org/nrg/xnat/event/listeners/methods/SmtpHandlerMethod.java @@ -2,18 +2,22 @@ package org.nrg.xnat.event.listeners.methods; import com.google.common.collect.ImmutableList; import org.nrg.mail.services.MailService; -import org.nrg.xdat.XDAT; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.nrg.xdat.preferences.NotificationsPreferences; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.mail.javamail.JavaMailSenderImpl; import org.springframework.stereotype.Component; -import javax.inject.Inject; import java.util.*; @Component public class SmtpHandlerMethod extends AbstractNotificationsPreferenceHandlerMethod { + @Autowired + public SmtpHandlerMethod(final NotificationsPreferences preferences, final JavaMailSenderImpl mailSender, final MailService mailService) { + _preferences = preferences; + this._mailSender = mailSender; + _mailService = mailService; + } + @Override public List<String> getHandledPreferences() { return PREFERENCES; @@ -28,45 +32,41 @@ public class SmtpHandlerMethod extends AbstractNotificationsPreferenceHandlerMet @Override public void handlePreference(final String preference, final String value) { - if(PREFERENCES.contains(preference)){ + if (PREFERENCES.contains(preference)) { updateSmtp(); } } - private void updateSmtp(){ - try { - mailSender.setHost(XDAT.getNotificationsPreferences().getHostname()); - mailSender.setPort(XDAT.getNotificationsPreferences().getPort()); - mailSender.setUsername(XDAT.getNotificationsPreferences().getUsername()); - mailSender.setPassword(XDAT.getNotificationsPreferences().getPassword()); - mailSender.setProtocol(XDAT.getNotificationsPreferences().getProtocol()); + private void updateSmtp() { + final Properties oldMailProperties = _mailSender.getJavaMailProperties(); + + final boolean smtpEnabled = _preferences.getSmtpEnabled(); + final boolean smtpAuth = _preferences.getSmtpAuth(); + final boolean startTls = _preferences.getSmtpStartTls(); + final String sslTrust = _preferences.getSmtpSSLTrust(); - Properties oldMailProperties = mailSender.getJavaMailProperties(); - boolean smtpEnabled = XDAT.getNotificationsPreferences().getSmtpEnabled(); - boolean smtpAuth = XDAT.getNotificationsPreferences().getSmtpAuth(); - boolean startTls = XDAT.getNotificationsPreferences().getSmtpStartTls(); - String sslTrust = XDAT.getNotificationsPreferences().getSmtpSSLTrust(); - _mailService.setSmtpEnabled(smtpEnabled); - oldMailProperties.setProperty("smtp.enabled",String.valueOf(smtpEnabled)); - oldMailProperties.setProperty("mail.smtp.auth",String.valueOf(smtpAuth)); - oldMailProperties.setProperty("mail.smtp.starttls.enable",String.valueOf(startTls)); - if(sslTrust!=null) { - oldMailProperties.setProperty("mail.smtp.ssl.trust", sslTrust); - } - mailSender.setJavaMailProperties(oldMailProperties); + _mailSender.setHost(_preferences.getHostname()); + _mailSender.setPort(_preferences.getPort()); + _mailSender.setUsername(_preferences.getUsername()); + _mailSender.setPassword(_preferences.getPassword()); + _mailSender.setProtocol(_preferences.getProtocol()); + _mailService.setSmtpEnabled(smtpEnabled); - } catch (Exception e1) { - _log.error("", e1); - } - } + oldMailProperties.setProperty("smtp.enabled", String.valueOf(smtpEnabled)); + oldMailProperties.setProperty("mail.smtp.auth", String.valueOf(smtpAuth)); + oldMailProperties.setProperty("mail.smtp.starttls.enable", String.valueOf(startTls)); - private static final Logger _log = LoggerFactory.getLogger(SmtpHandlerMethod.class); - private static final List<String> PREFERENCES = ImmutableList.copyOf(Arrays.asList("smtp.enabled", "host","port", "username","password", "protocol","smtp.enabled", "mail.smtp.auth","mail.smtp.starttls.enable", "mail.smtp.ssl.trust")); + if (sslTrust != null) { + oldMailProperties.setProperty("mail.smtp.ssl.trust", sslTrust); + } + + _mailSender.setJavaMailProperties(oldMailProperties); + } - @Inject - private JavaMailSenderImpl mailSender; + private static final List<String> PREFERENCES = ImmutableList.copyOf(Arrays.asList("smtp.enabled", "host", "port", "username", "password", "protocol", "smtp.enabled", "mail.smtp.auth", "mail.smtp.starttls.enable", "mail.smtp.ssl.trust")); - @Autowired - private MailService _mailService; + private final NotificationsPreferences _preferences; + private final JavaMailSenderImpl _mailSender; + private final MailService _mailService; } diff --git a/src/main/java/org/nrg/xnat/event/listeners/methods/UpdateSecurityFilterHandlerMethod.java b/src/main/java/org/nrg/xnat/event/listeners/methods/UpdateSecurityFilterHandlerMethod.java index 4dc18c9c6993fbc04481fb6e6858c0b0aff91c7a..76156d9245e8ac63d540d2b49c932aa2827adb80 100644 --- a/src/main/java/org/nrg/xnat/event/listeners/methods/UpdateSecurityFilterHandlerMethod.java +++ b/src/main/java/org/nrg/xnat/event/listeners/methods/UpdateSecurityFilterHandlerMethod.java @@ -1,21 +1,25 @@ package org.nrg.xnat.event.listeners.methods; import com.google.common.collect.ImmutableList; -import org.nrg.xdat.XDAT; +import org.nrg.xdat.preferences.SiteConfigPreferences; import org.nrg.xnat.security.FilterSecurityInterceptorBeanPostProcessor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; import org.springframework.stereotype.Component; -import javax.inject.Inject; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; @Component public class UpdateSecurityFilterHandlerMethod extends AbstractSiteConfigPreferenceHandlerMethod { + @Autowired + public UpdateSecurityFilterHandlerMethod(final SiteConfigPreferences preferences, @SuppressWarnings("SpringJavaAutowiringInspection") final FilterSecurityInterceptor interceptor, final FilterSecurityInterceptorBeanPostProcessor postProcessor) { + _preferences = preferences; + _interceptor = interceptor; + _postProcessor = postProcessor; + } + @Override public List<String> getHandledPreferences() { return PREFERENCES; @@ -37,17 +41,14 @@ public class UpdateSecurityFilterHandlerMethod extends AbstractSiteConfigPrefere private void updateSecurityFilter(){ if(_interceptor!=null && _postProcessor!=null){ - _interceptor.setSecurityMetadataSource(_postProcessor.getMetadataSource(XDAT.getSiteConfigPreferences().getRequireLogin())); + _interceptor.setSecurityMetadataSource(_postProcessor.getMetadataSource(_preferences.getRequireLogin())); } } - private static final Logger _log = LoggerFactory.getLogger(UpdateSecurityFilterHandlerMethod.class); - private static final List<String> PREFERENCES = ImmutableList.copyOf(Arrays.asList("requireLogin")); - - @Inject - private FilterSecurityInterceptor _interceptor; + private static final List<String> PREFERENCES = ImmutableList.copyOf(Collections.singletonList("requireLogin")); - @Inject - private FilterSecurityInterceptorBeanPostProcessor _postProcessor; + private final SiteConfigPreferences _preferences; + private final FilterSecurityInterceptor _interceptor; + private final FilterSecurityInterceptorBeanPostProcessor _postProcessor; } diff --git a/src/main/java/org/nrg/xnat/helpers/merge/AnonUtils.java b/src/main/java/org/nrg/xnat/helpers/merge/AnonUtils.java index 0a4209e2992ba6f01cd63f7edd3507a7fdb104b2..8d18fbf7f2c0d0ae6c02985b5d7a16f4aadac5a2 100644 --- a/src/main/java/org/nrg/xnat/helpers/merge/AnonUtils.java +++ b/src/main/java/org/nrg/xnat/helpers/merge/AnonUtils.java @@ -10,59 +10,62 @@ */ package org.nrg.xnat.helpers.merge; +import com.google.common.base.Joiner; import net.sf.ehcache.Cache; import net.sf.ehcache.CacheManager; import net.sf.ehcache.Element; import net.sf.ehcache.config.CacheConfiguration; import net.sf.ehcache.config.PersistenceConfiguration; - +import org.apache.commons.io.IOUtils; import org.nrg.config.entities.Configuration; import org.nrg.config.exceptions.ConfigServiceException; import org.nrg.config.services.ConfigService; import org.nrg.framework.constants.Scope; +import org.nrg.framework.exceptions.NrgServiceError; +import org.nrg.framework.exceptions.NrgServiceRuntimeException; +import org.nrg.framework.utilities.BasicXnatResourceLocator; import org.nrg.xdat.XDAT; -import org.nrg.xft.XFT; import org.nrg.xnat.helpers.editscript.DicomEdit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.core.io.Resource; import org.springframework.stereotype.Service; import javax.inject.Inject; - -import java.io.File; -import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; import java.util.List; @Service public class AnonUtils { - public AnonUtils() throws Exception { + public AnonUtils() throws Exception { if (_instance != null) { throw new Exception("The AnonUtils service is already initialized, try calling getInstance() instead."); } _instance = this; } - - public static AnonUtils getService() { - if (_instance == null) { - _instance = XDAT.getContextService().getBean(AnonUtils.class); - } - return _instance; - } - - public Configuration getScript(String path, Long project) { + + public static AnonUtils getService() { + if (_instance == null) { + _instance = XDAT.getContextService().getBean(AnonUtils.class); + } + return _instance; + } + + public Configuration getScript(String path, Long project) { if (logger.isDebugEnabled()) { logger.debug("Retrieving script for {}, {} for project: {}", DicomEdit.ToolName, path, project); } return project == null - ? _configService.getConfig(DicomEdit.ToolName, path) - : _configService.getConfig(DicomEdit.ToolName, path, Scope.Project, project.toString()); - } - - public boolean isEnabled(String path, Long project) { + ? _configService.getConfig(DicomEdit.ToolName, path) + : _configService.getConfig(DicomEdit.ToolName, path, Scope.Project, project.toString()); + } + + public boolean isEnabled(String path, Long project) { final Configuration config = project == null - ? _configService.getConfig(DicomEdit.ToolName, path) - : _configService.getConfig(DicomEdit.ToolName, path, Scope.Project, project.toString()); + ? _configService.getConfig(DicomEdit.ToolName, path) + : _configService.getConfig(DicomEdit.ToolName, path, Scope.Project, project.toString()); final boolean enabled = config.getStatus().equals(Configuration.ENABLED_STRING); if (logger.isDebugEnabled()) { @@ -73,12 +76,12 @@ public class AnonUtils { } } return enabled; - } - - public List<Configuration> getAllScripts (Long project) { + } + + public List<Configuration> getAllScripts(Long project) { final List<Configuration> scripts = project == null - ? _configService.getConfigsByTool(DicomEdit.ToolName) - : _configService.getConfigsByTool(DicomEdit.ToolName, Scope.Project, project.toString()); + ? _configService.getConfigsByTool(DicomEdit.ToolName) + : _configService.getConfigsByTool(DicomEdit.ToolName, Scope.Project, project.toString()); if (logger.isDebugEnabled()) { final String identifier = project == null ? "the site" : "project: " + project.toString(); @@ -91,10 +94,10 @@ public class AnonUtils { } } - return scripts; - } - - public void setProjectScript (String login, String path, String script, Long project) throws ConfigServiceException { + return scripts; + } + + public void setProjectScript(String login, String path, String script, Long project) throws ConfigServiceException { if (logger.isDebugEnabled()) { logger.debug("Setting script for {}, {} for project: {}", DicomEdit.ToolName, path, project); } @@ -103,63 +106,75 @@ public class AnonUtils { } else { _configService.replaceConfig(login, "", DicomEdit.ToolName, path, script, Scope.Project, project.toString()); } - } - - public void setSiteWideScript(String login, String path, String script) throws ConfigServiceException { + } + + public void setSiteWideScript(String login, String path, String script) throws ConfigServiceException { _configService.replaceConfig(login, "", DicomEdit.ToolName, path, script); AnonUtils.invalidateSitewideAnonCache(); - } - - public void enableSiteWide (String login, String path ) throws ConfigServiceException { + } + + public void enableSiteWide(String login, String path) throws ConfigServiceException { _configService.enable(login, "", DicomEdit.ToolName, path); AnonUtils.invalidateSitewideAnonCache(); - } - - public void enableProjectSpecific(String login, String path, Long project) throws ConfigServiceException { + } + + public void enableProjectSpecific(String login, String path, Long project) throws ConfigServiceException { if (project == null) { _configService.enable(login, "", DicomEdit.ToolName, path); } else { _configService.enable(login, "", DicomEdit.ToolName, path, Scope.Project, project.toString()); } - } - - public void disableSiteWide(String login, String path) throws ConfigServiceException { + } + + public void disableSiteWide(String login, String path) throws ConfigServiceException { _configService.disable(login, "", DicomEdit.ToolName, path); AnonUtils.invalidateSitewideAnonCache(); - } - - public void disableProjectSpecific(String login, String path, Long project) throws ConfigServiceException { + } + + public void disableProjectSpecific(String login, String path, Long project) throws ConfigServiceException { if (project == null) { _configService.disable(login, "", DicomEdit.ToolName, path); } else { _configService.disable(login, "", DicomEdit.ToolName, path, Scope.Project, project.toString()); } - } - - public static File getDefaultScript () throws FileNotFoundException { - final File def = new File (XFT.GetConfDir(), DEFAULT_ANON_SCRIPT); - if (def.exists()) { - return def; - } - else { - throw new FileNotFoundException("Default anon script: " + DEFAULT_ANON_SCRIPT + " not found in " + XFT.GetConfDir()); - } - } - - - /** - * Adds a cache of site wide anon scripts. This is currently used by GradualDicomImporter. - * @return The site anonymization script cache. - */ - public static Cache getSiteAnonCache() { + } + + public static String getDefaultScript() throws IOException { + final List<Resource> resources = BasicXnatResourceLocator.getResources(DEFAULT_ANON_SCRIPT); + if (resources.size() == 0) { + throw new NrgServiceRuntimeException(NrgServiceError.ConfigurationError, "Didn't find any default anonymization scripts at: " + DEFAULT_ANON_SCRIPT); + } else if (resources.size() > 1) { + boolean isFirst = true; + final StringBuilder duplicates = new StringBuilder(); + for (final Resource resource : resources) { + if (!isFirst) { + duplicates.append(", "); + } else { + isFirst = false; + } + duplicates.append(resource.getURI()); + } + throw new NrgServiceRuntimeException(NrgServiceError.ConfigurationError, "Found more than one \"default\" anonymization script: " + duplicates.toString()); + } + try (final InputStream input = resources.get(0).getInputStream()) { + return Joiner.on("\n").join(IOUtils.readLines(input, "UTF-8")); + } + } + + /** + * Adds a cache of site wide anon scripts. This is currently used by GradualDicomImporter. + * + * @return The site anonymization script cache. + */ + public static Cache getSiteAnonCache() { synchronized (cacheManager) { if (!cacheManager.cacheExists(cacheName)) { final CacheConfiguration config = new CacheConfiguration(cacheName, 0) - .copyOnRead(false).copyOnWrite(false) - .eternal(false) - .persistence(new PersistenceConfiguration().strategy(PersistenceConfiguration.Strategy.NONE)) - .timeToLiveSeconds(ANON_CACHE_EXPIRY_SECONDS) - .maxEntriesLocalHeap(MAX_ENTRIES_LOCAL_HEAP); + .copyOnRead(false).copyOnWrite(false) + .eternal(false) + .persistence(new PersistenceConfiguration().strategy(PersistenceConfiguration.Strategy.NONE)) + .timeToLiveSeconds(ANON_CACHE_EXPIRY_SECONDS) + .maxEntriesLocalHeap(MAX_ENTRIES_LOCAL_HEAP); final Cache cache = new Cache(config); cacheManager.addCache(cache); return cache; @@ -168,35 +183,35 @@ public class AnonUtils { } } } - - public static void invalidateSitewideAnonCache(){ - getSiteAnonCache().removeAndReturnElement(SITE_WIDE); - } - + + public static void invalidateSitewideAnonCache() { + getSiteAnonCache().removeAndReturnElement(SITE_WIDE); + } + public static Configuration getCachedSitewideAnon() throws Exception { - final Cache anonCache=getSiteAnonCache(); - - Element cached= anonCache.get(SITE_WIDE); - if(null!=cached){ - return (Configuration)cached.getObjectValue(); - }else{ + final Cache anonCache = getSiteAnonCache(); + + Element cached = anonCache.get(SITE_WIDE); + if (null != cached) { + return (Configuration) cached.getObjectValue(); + } else { Configuration c = AnonUtils.getService().getScript(path, null); anonCache.put(new Element(SITE_WIDE, c)); return c; } } - private static String path = DicomEdit.buildScriptPath(DicomEdit.ResourceScope.SITE_WIDE, null); + private static String path = DicomEdit.buildScriptPath(DicomEdit.ResourceScope.SITE_WIDE, null); private static final String cacheName = "scripts-anon"; - - private static final CacheManager cacheManager = CacheManager.getInstance(); - private static final String SITE_WIDE = "site-wide"; - private static final long ANON_CACHE_EXPIRY_SECONDS = 120; - private static final int MAX_ENTRIES_LOCAL_HEAP = 5000; + + private static final CacheManager cacheManager = CacheManager.getInstance(); + private static final String SITE_WIDE = "site-wide"; + private static final long ANON_CACHE_EXPIRY_SECONDS = 120; + private static final int MAX_ENTRIES_LOCAL_HEAP = 5000; private static final Logger logger = LoggerFactory.getLogger(AnonUtils.class); - private static final String DEFAULT_ANON_SCRIPT = "id.das"; + private static final String DEFAULT_ANON_SCRIPT = "classpath*:META-INF/xnat/defaults/**/id.das"; private static AnonUtils _instance; diff --git a/src/main/java/org/nrg/xnat/helpers/prearchive/PrearcDatabase.java b/src/main/java/org/nrg/xnat/helpers/prearchive/PrearcDatabase.java index 78ce9de3376f82fdceac4a945d548a6b22dba1b2..96d26ffc73dd7f44add003e5411c19a34d5ab98d 100644 --- a/src/main/java/org/nrg/xnat/helpers/prearchive/PrearcDatabase.java +++ b/src/main/java/org/nrg/xnat/helpers/prearchive/PrearcDatabase.java @@ -832,7 +832,6 @@ public final class PrearcDatabase { } } - private static void pruneDatabase() throws Exception { // construct list of timestamps with extant folders Set<String> timestamps = PrearcDatabase.getPrearchiveFolderTimestamps(); @@ -1792,6 +1791,19 @@ public final class PrearcDatabase { }); } + /** + * Gets the session from a given triple. + * + * @param triple The triple containing the session name, timestamp, and project. + * + * @return The corresponding session data. + * + * @throws Exception When something goes wrong. + */ + public static SessionData getSession(final SessionDataTriple triple) throws Exception { + return getSession(triple.getFolderName(), triple.getTimestamp(), triple.getProject()); + } + /** * Set the prearchive row that corresponds to the given session, timestamp, project triple to the given autoArchive setting. * @@ -1935,37 +1947,6 @@ public final class PrearcDatabase { }.run(); } - /** - * Either retrieve and existing session or create a new one and return it. - * <p/> - * This function is useful if the caller does not care which operation was performed. - * - * @param project - * @param suid - * @param s - * @param tsFile - * @param autoArchive - * - * @return - * - * @throws SQLException - * @throws SessionException - * @throws Exception - */ - public static SessionData getOrCreateSession(final String project, - final String suid, - final SessionData s, - final File tsFile, - final PrearchiveCode autoArchive) - throws SQLException, SessionException, Exception { - Either<SessionData, SessionData> result = PrearcDatabase.eitherGetOrCreateSession(s, tsFile, autoArchive); - if (result.isLeft()) { - return result.getLeft(); - } else { - return result.getRight(); - } - } - /** * Either retrieve and existing session or create a new one. If a session is created an Either object with the "Right" branch set is returned. If we just retrieve one that is already in the prearchive table an Either object with the "Left" branch set is returned. * <p/> @@ -1982,7 +1963,7 @@ public final class PrearcDatabase { */ public static synchronized Either<SessionData, SessionData> eitherGetOrCreateSession(final SessionData sessionData, final File tsFile, final PrearchiveCode autoArchive) throws SQLException, SessionException, Exception { return new PredicatedOp<SessionData, SessionData>() { - SessionData ss; + SessionData _sessionData; /** * Return the found session @@ -1991,7 +1972,7 @@ public final class PrearcDatabase { */ Either<SessionData, SessionData> trueOp() throws SQLException, SessionException, Exception { return new Either<SessionData, SessionData>() { - }.setRight(ss); + }.setRight(_sessionData); } /** @@ -2033,7 +2014,7 @@ public final class PrearcDatabase { } /** - * Test whether session exists. If it find the session the instance variable "SessionData ss" + * Test whether session exists. If it find the session the instance variable "SessionData _sessionData" * is initialized here. * * Originally this function initialized a "ResultSet r" instance variable and the "trueOp()" above @@ -2049,12 +2030,54 @@ public final class PrearcDatabase { constraints.add(DatabaseSession.TAG.searchSql(sessionData.getTag())); constraints.add(DatabaseSession.NAME.searchSql(sessionData.getName())); - ResultSet rs = pdb.executeQuery(null, DatabaseSession.findSessionSql(constraints.toArray(new String[constraints.size()])), null); - boolean found = rs.next(); - if (found) { - ss = DatabaseSession.fillSession(rs); + final ResultSet rs = pdb.executeQuery(null, DatabaseSession.findSessionSql(constraints.toArray(new String[constraints.size()])), null); + if (!rs.next()) { + if(logger.isDebugEnabled()) { + logger.debug("Found no existing session for " + sessionData.getSessionDataTriple().toString() + ". A new session data object will be created for data reception."); + } + return false; + } + + final SessionData sessionData = DatabaseSession.fillSession(rs); + + final PrearcStatus status = sessionData.getStatus(); + if (PrearcStatus.RECEIVING.equals(status)|| PrearcStatus.RECEIVING_INTERRUPT.equals(status)) { + // Obviously if we're receiving we're fine. + if(logger.isDebugEnabled()) { + logger.debug("Receiving incoming data for session " + sessionData.getSessionDataTriple().toString() + ", which is currently in " + status + " state, which is totally fine."); + } + _sessionData = sessionData; + return true; + } + if (status == PrearcStatus.BUILDING) { + // If the session is currently building, then set this session to RECEIVING_INTERRUPT, + // which will allow it to continue receiving but prevent autoarchiving or session + // splitting afterwards. + if(logger.isWarnEnabled()) { + logger.warn("Receiving incoming data for session " + sessionData.getSessionDataTriple().toString() + " in BUILDING state, setting status to RECEIVING_INTERRUPT to block autoarchive and other operations and allow continuation of data reception."); + } + PoolDBUtils.ExecuteNonSelectQuery(DatabaseSession.updateSessionStatusSQL(sessionData.getName(), sessionData.getTimestamp(), sessionData.getProject(), PrearcStatus.RECEIVING_INTERRUPT), null, null); + _sessionData = sessionData; + return true; + } + if (status.isInterruptable()) { + // If the session is interruptable, which means it's not receiving but it's OK to set it to + // receiving (ready, in error, or in conflict), that's OK. Set to RECEIVING and return the + // session. Any other issues will be worked out (or re-occur) later. + if (logger.isInfoEnabled()) { + logger.info("Receiving incoming data for session " + sessionData.getSessionDataTriple().toString() + ", which is currently in the interruptable " + status + " state. Setting status to RECEIVING to allow continuation of data reception."); + } + PoolDBUtils.ExecuteNonSelectQuery(DatabaseSession.updateSessionStatusSQL(sessionData.getName(), sessionData.getTimestamp(), sessionData.getProject(), PrearcStatus.RECEIVING), null, null); + _sessionData = sessionData; + return true; } - return found; + // If the status isn't interruptable, e.g. we're archiving or moving or deleting or whatever, + // then return false: we'll create a new session to receive the incoming data. This may require + // a merge later, but should prevent data loss. + if (logger.isWarnEnabled()) { + logger.warn("Receiving incoming data for session " + sessionData.getSessionDataTriple().toString() + ", which is currently in the non-interruptable " + status + " state. Creating a new RECEIVING session to allow continuation of data reception."); + } + return false; } }.run(); } @@ -2290,6 +2313,19 @@ public final class PrearcDatabase { return StringUtils.join(as.toArray(new String[as.size()]), ","); } + /** + * Update the last modified time of the session to the current time. + * + * @param triple The triple containing the session name, timestamp, and project. + * + * @throws SQLException + * @throws SessionException + * @throws Exception + */ + public static void updateTimestamp(final SessionDataTriple triple) throws SQLException, SessionException, Exception { + updateTimestamp(triple.getFolderName(), triple.getTimestamp(), triple.getProject()); + } + /** * Update the last modified time of the session to the current time. * @@ -2301,8 +2337,7 @@ public final class PrearcDatabase { * @throws SessionException * @throws Exception */ - - public static void setLastModifiedTime(String sess, String timestamp, String proj) throws SQLException, SessionException, Exception { + public static void updateTimestamp(String sess, String timestamp, String proj) throws SQLException, SessionException, Exception { modifySession(sess, timestamp, proj, new SessionOp<java.lang.Void>() { public Void op() throws SQLException, Exception { return null; diff --git a/src/main/java/org/nrg/xnat/helpers/prearchive/PrearcTableBuilder.java b/src/main/java/org/nrg/xnat/helpers/prearchive/PrearcTableBuilder.java index 816eb750e55c06485daffba625fc28f990aff442..cf8da7e186f76604b8e250871ab417c641a29dd5 100644 --- a/src/main/java/org/nrg/xnat/helpers/prearchive/PrearcTableBuilder.java +++ b/src/main/java/org/nrg/xnat/helpers/prearchive/PrearcTableBuilder.java @@ -8,10 +8,6 @@ * * Last modified 7/19/13 4:17 PM */ - -/** - * - */ package org.nrg.xnat.helpers.prearchive; import org.apache.commons.lang3.StringUtils; @@ -19,7 +15,6 @@ import org.nrg.framework.constants.PrearchiveCode; import org.nrg.xdat.bean.XnatImagesessiondataBean; import org.nrg.xdat.bean.reader.XDATXMLReader; import org.nrg.xdat.model.XnatImagesessiondataI; -import org.nrg.xft.XFTTable; import org.nrg.xnat.helpers.prearchive.PrearcUtils.PrearcStatus; import org.nrg.xnat.turbine.utils.ArcSpecManager; import org.slf4j.Logger; @@ -33,304 +28,253 @@ import java.util.*; /** * @author timo - * */ public class PrearcTableBuilder implements PrearcTableBuilderI { - static Logger logger = LoggerFactory.getLogger(PrearcTableBuilder.class); - - public final static String[] PREARC_HEADERS = {"project".intern(),"last_mod".intern(),"uploaded".intern(),"scan_date".intern(),"scan_time".intern(),"subject".intern(),"session".intern(),"status".intern(),"url".intern(),"visit".intern(),"protocol".intern(),"TIMEZONE".intern(),"SOURCE".intern()}; - - public static Object[] buildRow(final Session s,final String urlBase){ - Object[] row = new Object[PREARC_HEADERS.length]; - row[0]=s.getProject(); - row[1]=s.getLastBuiltDate(); - row[2]=s.getUploadDate(); - row[3]=s.getDate(); - row[4]=s.getTime(); - row[5]=PrearcTableBuilder.Session.pickSubjectName(s); - row[6]=PrearcTableBuilder.Session.pickSessionName(s); - row[7]=s.getStatus(); - row[8]=StringUtils.join(new String[]{urlBase,"/".intern(),s.getTimestamp(),"/".intern(),s.getFolderName()}); - row[9]=s.getVisit(); - row[10]=s.getProtocol(); - row[11]=s.getTimeZone(); - row[12]=s.getSource(); - - return row; - } - - - public static XnatImagesessiondataBean parseSession(final File s) throws IOException, SAXException{ - XDATXMLReader parser = new XDATXMLReader(); - return (XnatImagesessiondataBean) parser.parse(s); - } - - public class ProjectPrearchive implements ProjectPrearchiveI { - private Date lastMod; - private XFTTable content; - - public ProjectPrearchive(final Date l, final XFTTable c){ - lastMod=l; - content=c; - } - - /* (non-Javadoc) - * @see org.nrg.xnat.helpers.prearchive.ProjectPrearchiveI#getLastMod() - */ - @Override - public Date getLastMod() { - return lastMod; - } - - /* (non-Javadoc) - * @see org.nrg.xnat.helpers.prearchive.ProjectPrearchiveI#getContent() - */ - @Override - public XFTTable getContent() { - return content; - } - - - } - - @Override - public String[] getColumns() { - return PREARC_HEADERS; - } - - - public static String printSession (Session s) { - ArrayList<String> as = new ArrayList<String>(); - as.add("--Session--"); - as.add("Name : " + s.getFolderName()); - as.add("Status : " + s.getStatus()); - as.add("SubjectId: " + s.getSubjectId()); - as.add("Scan Time : " + s.getTimestamp()); - as.add("Uploaded : " + s.getUploadDate().toString()); - return StringUtils.join(as.toArray(new String[as.size()]), "\n"); - } - - public static class Session implements Comparable<Session> { - private final File sessionXML; - - private XnatImagesessiondataI session=null; - - private SessionData data = new SessionData(); - - //passed in project should override what is in the session xml, if it exists - Session(final File sessdir, final String project) { - data.setFolderName(sessdir.getName()); - - sessionXML = new File(sessdir.getPath() + ".xml"); - if (sessionXML.exists()) { - data.setLastBuiltDate(new Date(sessionXML.lastModified())); - } else { - data.setLastBuiltDate(new Date()); - } - - Date t_uploadDate; - try { - t_uploadDate = PrearcUtils.parseTimestampDirectory(sessdir.getParentFile().getName()); - } catch (final ParseException e) { - logger.error("Unable to parse upload date from session parent " + sessdir.getParentFile(), e); - t_uploadDate = null; - } - data.setUploadDate(t_uploadDate); - - - data.setStatus(PrearcUtils.checkSessionStatus(sessionXML)); - - if(!sessionXML.exists() || sessionXML.length() == 0){ - if(project!=null){ - session=new XnatImagesessiondataBean(); - session.setProject(project); - } - if(PrearcStatus.potentiallyReady(data.getStatus()))data.setStatus(PrearcStatus.RECEIVING); - }else{ - try { - session=parseSession(sessionXML); - - session.setProject(project); - - data.setTag(session.getUid()); - - final String sessionID = session.getId(); - if (null == sessionID || "".equals(sessionID) || "NULL".equals(sessionID)) { - data.setStatus(PrearcStatus.READY); - } else { - data.setStatus(PrearcStatus.ARCHIVING); - } - } catch (Exception e) { - if(PrearcStatus.potentiallyReady(data.getStatus())){ - // The following accounts for the case where a project was passed in but the session XML is unparseable for some reason (eg. it is empty). - // In that case use the project name passed in. - if (project != null && (data.getProject() == null || !data.getProject().equals(project))) { - data.setProject(project); - } - data.setStatus(PrearcStatus.ERROR); - - PrearcUtils.log(sessdir, e); - } - } - } - } - - public SessionData getSessionData (String urlBase) { - //populate the rest of the session data object. - data.setProject(this.getProject()); - data.setScan_date(this.getDate()); - data.setScan_time(this.getTime()); - data.setSubject(PrearcTableBuilder.Session.pickSubjectName(this)); - data.setName(PrearcTableBuilder.Session.pickSessionName(this)); - data.setFolderName(this.getFolderName()); - data.setTag(this.getTag()); - data.setAutoArchive(this.getPrearchiveCode()); - data.setUrl(PrearcUtils.makeUri(urlBase, data.getTimestamp(), data.getFolderName())); - data.setVisit(this.getVisit()); - data.setProtocol(this.getProtocol()); - data.setTimeZone(this.getTimeZone()); - data.setSource(this.getSource()); - return this.data; - } - - public static String pickSubjectName(final PrearcTableBuilder.Session s) { - String ret = ""; - if (StringUtils.isNotEmpty(s.getSubjectId())) { - ret = s.getSubjectId(); - } - if (StringUtils.isEmpty(ret) && StringUtils.isNotEmpty(s.getPatientName())) { - ret = s.getPatientName(); - } - - return ret; - } - - public static String pickSessionName(final PrearcTableBuilder.Session s) { - String ret = ""; - if (StringUtils.isNotEmpty(s.getLabel())) { - ret = s.getLabel(); - } - if (StringUtils.isEmpty(ret) && StringUtils.isNotEmpty(s.getPatientId())) { - ret = s.getPatientId(); - } - - if (StringUtils.isEmpty(ret)){ - return s.getFolderName(); - } - return ret; - } - - public void setFolderName(String name) { - this.data.setFolderName(name); - } - - public String getFolderName() { - return data.getFolderName(); - } - - public void setTag(String name) { - this.data.setTag(name); - } - - public String getTag() { - return data.getTag(); - } - - public void setSessionName(String name) { - this.data.setName(name); - } - - public String getSessionName() { - return data.getName(); - } - - public Date getLastBuiltDate() { - return data.getLastBuiltDate(); - } - public void setLastBuiltDate(Date lastBuiltDate) { - data.setLastBuiltDate(lastBuiltDate); - } - - public Date getUploadDate() { - return data.getUploadDate(); - } - - public String getTimestamp() { - return data.getTimestamp(); - } - - public void setTimestamp(String timestamp) { - this.data.setTimestamp(timestamp); - } - - public String getProject(){ - // Get the project specified in the session.xml. - // If the session.xml file couldn't be parsed return the project field in the the local SessionData object - // which holds the optional project name passed to the constructor in case of an unparseable session.xml. - if (session != null) { - return session.getProject(); - } - else { - if (data != null && data.getProject() != null) { - return data.getProject(); - } - else { - return null; - } - } - } - - public Object getDate(){ - return (session!=null)?session.getDate():null; - } - - public Object getTime(){ - return (session!=null)?session.getTime():null; - } - - public String getSubjectId(){ - return (session!=null)?session.getSubjectId():null; - } - - public String getLabel(){ - return (session!=null)?session.getLabel():null; - - } - public String getVisit(){ - return (session!=null)?session.getVisit():null; - - } - public String getProtocol(){ - return (session!=null)?session.getProtocol():null; - - } - public String getTimeZone(){ - //no need to keep timezone in the image session. - //return (session!=null)?session.getTimeZone():null; - return null; - } - public String getSource(){ - return null; - - } - public String getPatientId() { - return (session!=null)?session.getDcmpatientid():null; - } - - public String getPatientName() { - return (session!=null)?session.getDcmpatientname():null; - } - - public PrearcStatus getStatus(){ - return data.getStatus(); - } - - public File getSessionXML(){ - return this.sessionXML; - } - - public PrearchiveCode getPrearchiveCode() { - final String project = this.getProject(); + static Logger logger = LoggerFactory.getLogger(PrearcTableBuilder.class); + + public final static String[] PREARC_HEADERS = {"project", "last_mod", "uploaded", "scan_date", "scan_time", "subject", "session", "status", "url", "visit", "protocol", "TIMEZONE", "SOURCE"}; + + public static XnatImagesessiondataBean parseSession(final File s) throws IOException, SAXException { + XDATXMLReader parser = new XDATXMLReader(); + return (XnatImagesessiondataBean) parser.parse(s); + } + + @Override + public String[] getColumns() { + return PREARC_HEADERS; + } + + public static class Session implements Comparable<Session> { + private final File sessionXML; + + private XnatImagesessiondataI session = null; + + private SessionData data = new SessionData(); + + //passed in project should override what is in the session xml, if it exists + Session(final File folder, final String project) { + data.setFolderName(folder.getName()); + + sessionXML = new File(folder.getPath() + ".xml"); + if (sessionXML.exists()) { + data.setLastBuiltDate(new Date(sessionXML.lastModified())); + } else { + data.setLastBuiltDate(new Date()); + } + + Date t_uploadDate; + try { + t_uploadDate = PrearcUtils.parseTimestampDirectory(folder.getParentFile().getName()); + } catch (final ParseException e) { + logger.error("Unable to parse upload date from session parent " + folder.getParentFile(), e); + t_uploadDate = null; + } + data.setUploadDate(t_uploadDate); + + data.setStatus(PrearcUtils.checkSessionStatus(sessionXML)); + + if (!sessionXML.exists() || sessionXML.length() == 0) { + if (project != null) { + session = new XnatImagesessiondataBean(); + session.setProject(project); + } + if (PrearcStatus.potentiallyReady(data.getStatus())) { + data.setStatus(PrearcStatus.RECEIVING); + } + } else { + try { + session = parseSession(sessionXML); + + session.setProject(project); + + data.setTag(session.getUid()); + + final String sessionID = session.getId(); + if (null == sessionID || "".equals(sessionID) || "NULL".equals(sessionID)) { + data.setStatus(PrearcStatus.READY); + } else { + data.setStatus(PrearcStatus.ARCHIVING); + } + } catch (Exception e) { + if (PrearcStatus.potentiallyReady(data.getStatus())) { + // The following accounts for the case where a project was passed in but the session XML is unparseable for some reason (eg. it is empty). + // In that case use the project name passed in. + if (project != null && (data.getProject() == null || !data.getProject().equals(project))) { + data.setProject(project); + } + data.setStatus(PrearcStatus.ERROR); + + PrearcUtils.log(folder, e); + } + } + } + } + + public SessionData getSessionData(String urlBase) { + //populate the rest of the session data object. + data.setProject(getProject()); + data.setScan_date(getDate()); + data.setScan_time(getTime()); + data.setSubject(PrearcTableBuilder.Session.pickSubjectName(this)); + data.setName(PrearcTableBuilder.Session.pickSessionName(this)); + data.setFolderName(getFolderName()); + data.setTag(getTag()); + data.setAutoArchive(getPrearchiveCode()); + data.setUrl(PrearcUtils.makeUri(urlBase, data.getTimestamp(), data.getFolderName())); + data.setVisit(getVisit()); + data.setProtocol(getProtocol()); + data.setTimeZone(getTimeZone()); + data.setSource(getSource()); + return data; + } + + public static String pickSubjectName(final PrearcTableBuilder.Session s) { + String ret = ""; + if (StringUtils.isNotEmpty(s.getSubjectId())) { + ret = s.getSubjectId(); + } + if (StringUtils.isEmpty(ret) && StringUtils.isNotEmpty(s.getPatientName())) { + ret = s.getPatientName(); + } + + return ret; + } + + public static String pickSessionName(final PrearcTableBuilder.Session s) { + String ret = ""; + if (StringUtils.isNotEmpty(s.getLabel())) { + ret = s.getLabel(); + } + if (StringUtils.isEmpty(ret) && StringUtils.isNotEmpty(s.getPatientId())) { + ret = s.getPatientId(); + } + + if (StringUtils.isEmpty(ret)) { + return s.getFolderName(); + } + return ret; + } + + @SuppressWarnings("unused") + public void setFolderName(String name) { + data.setFolderName(name); + } + + public String getFolderName() { + return data.getFolderName(); + } + + public void setTag(String name) { + data.setTag(name); + } + + public String getTag() { + return data.getTag(); + } + + @SuppressWarnings("unused") + public void setSessionName(String name) { + data.setName(name); + } + + @SuppressWarnings("unused") + public String getSessionName() { + return data.getName(); + } + + public Date getLastBuiltDate() { + return data.getLastBuiltDate(); + } + + @SuppressWarnings("unused") + public void setLastBuiltDate(Date lastBuiltDate) { + data.setLastBuiltDate(lastBuiltDate); + } + + @SuppressWarnings("unused") + public Date getUploadDate() { + return data.getUploadDate(); + } + + public String getTimestamp() { + return data.getTimestamp(); + } + + public void setTimestamp(String timestamp) { + data.setTimestamp(timestamp); + } + + public String getProject() { + // Get the project specified in the session.xml. + // If the session.xml file couldn't be parsed return the project field in the the local SessionData object + // which holds the optional project name passed to the constructor in case of an unparseable session.xml. + if (session != null) { + return session.getProject(); + } else { + if (data != null && data.getProject() != null) { + return data.getProject(); + } else { + return null; + } + } + } + + public Object getDate() { + return (session != null) ? session.getDate() : null; + } + + public Object getTime() { + return (session != null) ? session.getTime() : null; + } + + public String getSubjectId() { + return (session != null) ? session.getSubjectId() : null; + } + + public String getLabel() { + return (session != null) ? session.getLabel() : null; + + } + + public String getVisit() { + return (session != null) ? session.getVisit() : null; + + } + + public String getProtocol() { + return (session != null) ? session.getProtocol() : null; + + } + + public String getTimeZone() { + //no need to keep timezone in the image session. + //return (session!=null)?session.getTimeZone():null; + return null; + } + + public String getSource() { + return null; + + } + + public String getPatientId() { + return (session != null) ? session.getDcmpatientid() : null; + } + + public String getPatientName() { + return (session != null) ? session.getDcmpatientname() : null; + } + + public PrearcStatus getStatus() { + return data.getStatus(); + } + + public File getSessionXML() { + return sessionXML; + } + + public PrearchiveCode getPrearchiveCode() { + final String project = getProject(); if (project == null || project.equals(PrearcUtils.COMMON)) { logger.info("Found null or unassigned project, returning prearchive code of Manual"); return PrearchiveCode.Manual; // Unassigned projects will not have a known prearchive code @@ -338,56 +282,68 @@ public class PrearcTableBuilder implements PrearcTableBuilderI { final Integer prearchiveCode = ArcSpecManager.GetInstance().getPrearchiveCodeForProject(project); if (prearchiveCode == null) { if (logger.isWarnEnabled()) { - logger.warn("Found a prearchive entry " + this.getFolderName() + " with a project that didn't return an archive code: " + project); + logger.warn("Found a prearchive entry " + getFolderName() + " with a project that didn't return an archive code: " + project); } return PrearchiveCode.Manual; } return PrearchiveCode.code(prearchiveCode); } - - /* - * (non-Javadoc) - * - * @see java.lang.Comparable#compareTo(java.lang.Object) - */ - @Override - public int compareTo(final Session other) { - return getLastBuiltDate().compareTo(other.getLastBuiltDate()); - } - } - - public SortedMap<Date, Collection<Session>> getPrearcSessions(final File prearcDir) throws IOException, SAXException { - final SortedMap<Date, Collection<Session>> sessions = new TreeMap<Date, Collection<Session>>(); - if(PrearcUtils.isTimestampDirectory.accept(prearcDir)){ - for (final File sessdir : prearcDir.listFiles(PrearcUtils.isDirectory)) { - final Session session = buildSessionObject(sessdir,prearcDir.getName(),null); - - final Date builtDate = session.getLastBuiltDate(); - - if (!sessions.containsKey(builtDate)) { - sessions.put(builtDate, new ArrayList<Session>(1)); - } - sessions.get(builtDate).add(session); - } - }else{ - for (final File tsdir : prearcDir.listFiles(PrearcUtils.isTimestampDirectory)) { - for (final File sessdir : tsdir.listFiles(PrearcUtils.isDirectory)) { - final Session session = buildSessionObject(sessdir,tsdir.getName(),prearcDir.getName()); - - final Date builtDate = session.getLastBuiltDate(); - if (!sessions.containsKey(builtDate)) { - sessions.put(builtDate, new ArrayList<Session>(1)); - } - sessions.get(builtDate).add(session); - } - } - } - return sessions; - } - - public static Session buildSessionObject(final File sessdir,final String timestamp, final String project){ - final Session session = new Session(sessdir,project); - session.setTimestamp(timestamp); - return session; - } + + /* + * (non-Javadoc) + * + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + @Override + public int compareTo(final Session other) { + return getLastBuiltDate().compareTo(other.getLastBuiltDate()); + } + } + + public SortedMap<Date, Collection<Session>> getPrearcSessions(final File prearcDir) throws IOException, SAXException { + if (prearcDir == null) { + return null; + } + final SortedMap<Date, Collection<Session>> sessions = new TreeMap<>(); + if (PrearcUtils.isTimestampDirectory.accept(prearcDir)) { + final File[] folders = prearcDir.listFiles(PrearcUtils.isDirectory); + if (folders != null) { + for (final File folder : folders) { + final Session session = buildSessionObject(folder, prearcDir.getName(), null); + + final Date builtDate = session.getLastBuiltDate(); + + if (!sessions.containsKey(builtDate)) { + sessions.put(builtDate, new ArrayList<Session>(1)); + } + sessions.get(builtDate).add(session); + } + } + } else { + final File[] timestampFolders = prearcDir.listFiles(PrearcUtils.isTimestampDirectory); + if (timestampFolders != null) { + for (final File timestampFolder : timestampFolders) { + final File[] folders = timestampFolder.listFiles(PrearcUtils.isDirectory); + if (folders != null) { + for (final File folder : folders) { + final Session session = buildSessionObject(folder, timestampFolder.getName(), prearcDir.getName()); + + final Date builtDate = session.getLastBuiltDate(); + if (!sessions.containsKey(builtDate)) { + sessions.put(builtDate, new ArrayList<Session>(1)); + } + sessions.get(builtDate).add(session); + } + } + } + } + } + return sessions; + } + + public static Session buildSessionObject(final File folder, final String timestamp, final String project) { + final Session session = new Session(folder, project); + session.setTimestamp(timestamp); + return session; + } } diff --git a/src/main/java/org/nrg/xnat/helpers/prearchive/PrearcUtils.java b/src/main/java/org/nrg/xnat/helpers/prearchive/PrearcUtils.java index b9bf650b3a1bf215e7e83b646028db70ec8581fe..3a1d0f3615ebef0672207a10dd2dd4b30f7c1111 100644 --- a/src/main/java/org/nrg/xnat/helpers/prearchive/PrearcUtils.java +++ b/src/main/java/org/nrg/xnat/helpers/prearchive/PrearcUtils.java @@ -92,38 +92,48 @@ public class PrearcUtils { public enum PrearcStatus { ARCHIVING, - BUILDING, - CONFLICT, + BUILDING(true), + CONFLICT(true), DELETING, - ERROR, + ERROR(true), MOVING, - READY, - RECEIVING, + READY(true), + RECEIVING(true), + RECEIVING_INTERRUPT(true), SEPARATING, QUEUED_ARCHIVING, - QUEUED_BUILDING, + QUEUED_BUILDING(true), QUEUED_DELETING, QUEUED_MOVING, QUEUED_SEPARATING, _ARCHIVING, - _BUILDING, - _CONFLICT, + _BUILDING(true), + _CONFLICT(true), _DELETING, _MOVING, - _RECEIVING, - _SEPARATING, - - _QUEUED_ARCHIVING, - _QUEUED_BUILDING, - _QUEUED_DELETING, - _QUEUED_MOVING, - _QUEUED_SEPARATING; + _RECEIVING(true), + _RECEIVING_INTERRUPT(true), + _SEPARATING; public static boolean potentiallyReady(PrearcStatus status) { return (status == null || status.equals(READY)); } + + public boolean isInterruptable() { + return _interruptable; + } + + PrearcStatus() { + this(true); + } + + PrearcStatus(final boolean interruptable) { + _interruptable = interruptable; + } + + private final boolean _interruptable; } private static Logger logger() { @@ -135,7 +145,7 @@ public class PrearcUtils { public static Map<PrearcStatus, PrearcStatus> createInProcessMap() { Map<PrearcStatus, PrearcStatus> map = new HashMap<>(); for (PrearcStatus s : PrearcStatus.values()) { - if (s != PrearcStatus.READY && s != PrearcStatus.ERROR && s.toString().charAt(0) != '_') { + if (s != PrearcStatus.READY && s != PrearcStatus.ERROR && s.toString().charAt(0) != '_' && !s.toString().startsWith("QUEUED_")) { map.put(s, PrearcStatus.valueOf("_" + s.name())); } } diff --git a/src/main/java/org/nrg/xnat/helpers/prearchive/SessionDataTriple.java b/src/main/java/org/nrg/xnat/helpers/prearchive/SessionDataTriple.java index ba0c0e318dd0de685102004ddb96dcf801f535c5..284210df0bfb636348653e4d11ae8d150555ae19 100644 --- a/src/main/java/org/nrg/xnat/helpers/prearchive/SessionDataTriple.java +++ b/src/main/java/org/nrg/xnat/helpers/prearchive/SessionDataTriple.java @@ -10,98 +10,130 @@ */ package org.nrg.xnat.helpers.prearchive; +import org.apache.commons.lang3.StringUtils; import org.nrg.xnat.restlet.XNATApplication; import org.nrg.xnat.restlet.actions.PrearcImporterA.PrearcSession; -import java.io.File; import java.io.Serializable; import java.net.MalformedURLException; -import java.util.HashMap; import java.util.Map; public class SessionDataTriple implements Serializable { + public SessionDataTriple() { + // Default constructor + } + + public SessionDataTriple(final String folderName, final String timestamp, final String project) { + setFolderName(folderName); + setTimestamp(timestamp); + setProject(project); + } + + public static SessionDataTriple fromMap(Map<String, String> m) { + return new SessionDataTriple().setFolderName(m.get("SESSION_LABEL")) + .setProject(m.get("PROJECT_ID")) + .setTimestamp(m.get("SESSION_TIMESTAMP")); + } + + public static SessionDataTriple fromURI(final String uri) throws MalformedURLException { + final PrearcUriParserUtils.SessionParser parser = new PrearcUriParserUtils.SessionParser(new PrearcUriParserUtils.UriParser(XNATApplication.PREARC_SESSION_URI)); + return SessionDataTriple.fromMap(parser.readUri(uri)); + } + + public static SessionDataTriple fromPrearcSession(final PrearcSession session) { + return new SessionDataTriple().setFolderName(session.getFolderName()) + .setProject(session.getProject()) + .setTimestamp(session.getTimestamp()); + } + + public String getFolderName() { + return _folderName; + } + + public SessionDataTriple setFolderName(final String name) { + _folderName = name; + return this; + } + + public SessionDataTriple setFolderName(final Object object) { + if (object != null) { + setFolderName(object.toString()); + } + return this; + } + + public String getTimestamp() { + return _timestamp; + } + + public SessionDataTriple setTimestamp(final String timestamp) { + _timestamp = timestamp; + return this; + } + + public SessionDataTriple setTimestamp(final Object object) { + if (object != null) { + setTimestamp(object.toString()); + } + return this; + } + + public String getProject() { + return _project; + } + + public SessionDataTriple setProject(final String project) { + if (StringUtils.isNotBlank(project)) { + _project = project; + } else { + _project = PrearcUtils.COMMON; + } + return this; + } + + public SessionDataTriple setProject(final Object object) { + setProject(object.toString()); + return this; + } + + @Override + public String toString() { + return _folderName + ':' + _timestamp + ':' + _project; + } + + @Override + public boolean equals(final Object other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + + final SessionDataTriple that = (SessionDataTriple) other; + + return _folderName != null + ? _folderName.equals(that._folderName) + : that._folderName == null + && (_timestamp != null + ? _timestamp.equals(that._timestamp) + : that._timestamp == null + && (_project != null + ? _project.equals(that._project) + : that._project == null)); + } + + @Override + public int hashCode() { + int result = _folderName != null ? _folderName.hashCode() : 0; + result = 31 * result + (_timestamp != null ? _timestamp.hashCode() : 0); + result = 31 * result + (_project != null ? _project.hashCode() : 0); + return result; + } + private static final long serialVersionUID = 7764386535994779313L; - private String folderName; - private String timestamp; - private String project; - - public SessionDataTriple() { - } - public String getFolderName() { - return this.folderName; - } - public SessionDataTriple setFolderName(String name) { - this.folderName = name; - return this; - } - public SessionDataTriple setFolderName(Object o) { - if (o != null) { - this.setFolderName((String)o); - } - return this; - } - - public String getTimestamp() { - return this.timestamp; - } - public SessionDataTriple setTimestamp(String timestamp) { - this.timestamp = timestamp; - return this; - } - public SessionDataTriple setTimestamp(Object o) { - if (o != null) { - this.setTimestamp((String)o); - } - return this; - } - - public String getProject() { - return this.project; - } - public SessionDataTriple setProject(String project) { - if (project != null) { - this.project = project; - } - else { - this.project = PrearcUtils.COMMON; - } - return this; - } - public SessionDataTriple setProject(Object o) { - this.setProject((String)o); - return this; - } - - public static SessionDataTriple makeTriple (String sess, String timestamp, String proj) { - return new SessionDataTriple().setFolderName(sess).setProject(proj).setTimestamp(timestamp); - } - - public Map<String,String> toMap () { - Map<String,String> ret = new HashMap<String,String>(); - ret.put("PROJECT_ID", this.getProject()); - ret.put("SESSION_TIMESTAMP", this.getTimestamp()); - ret.put("SESSION_LABEL", this.getFolderName()); - return ret; - } - - public static SessionDataTriple fromMap (Map<String,String> m) { - return new SessionDataTriple().setFolderName(m.get("SESSION_LABEL")) - .setProject(m.get("PROJECT_ID")) - .setTimestamp(m.get("SESSION_TIMESTAMP")); - } - public static SessionDataTriple fromFile (final String project, final File f) { - return new SessionDataTriple().setFolderName(f.getName()) - .setProject(project) - .setTimestamp(f.getParentFile().getName()); - } - public static SessionDataTriple fromURI (final String uri) throws MalformedURLException { - final PrearcUriParserUtils.SessionParser parser = new PrearcUriParserUtils.SessionParser(new PrearcUriParserUtils.UriParser(XNATApplication.PREARC_SESSION_URI)); - return SessionDataTriple.fromMap(parser.readUri(uri)); - } - public static SessionDataTriple fromPrearcSession (final PrearcSession session) { - return new SessionDataTriple().setFolderName(session.getFolderName()) - .setProject(session.getProject()) - .setTimestamp(session.getTimestamp()); - } + private String _folderName; + private String _timestamp; + private String _project; } \ No newline at end of file diff --git a/src/main/java/org/nrg/xnat/helpers/prearchive/SessionXMLRebuilder.java b/src/main/java/org/nrg/xnat/helpers/prearchive/SessionXMLRebuilder.java index f9333c785ca42f67da612501c4e994c0947224c8..6ba5c672287c81b4fd6d5a55ab65db893efa1be1 100644 --- a/src/main/java/org/nrg/xnat/helpers/prearchive/SessionXMLRebuilder.java +++ b/src/main/java/org/nrg/xnat/helpers/prearchive/SessionXMLRebuilder.java @@ -11,9 +11,11 @@ package org.nrg.xnat.helpers.prearchive; import org.apache.commons.lang3.StringUtils; +import org.nrg.framework.exceptions.NrgServiceRuntimeException; import org.nrg.xdat.XDAT; import org.nrg.xft.exception.InvalidPermissionException; import org.nrg.xft.security.UserI; +import org.nrg.xnat.services.XnatAppInfo; import org.nrg.xnat.services.messaging.prearchive.PrearchiveOperationRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,70 +29,93 @@ import java.util.Calendar; import java.util.List; public class SessionXMLRebuilder implements Runnable { - public SessionXMLRebuilder(final Provider<UserI> provider, final double interval, final JmsTemplate jmsTemplate) { + public SessionXMLRebuilder(final Provider<UserI> provider, final XnatAppInfo appInfo, final JmsTemplate jmsTemplate, final double interval) { _provider = provider; + _appInfo = appInfo; _interval = interval; _jmsTemplate = jmsTemplate; } @Override public void run() { - final UserI user = _provider.get(); - logger.trace("Running prearc job as {}", user.getLogin()); - List<SessionData> sds = null; - long now = Calendar.getInstance().getTimeInMillis(); + if (!_appInfo.isInitialized()) { + if (!_markedUninitialized) { + logger.warn("Application is not yet initialized, session XML rebuild operation delayed until initialization completed."); + _markedUninitialized = true; + } + return; + } + try { - if (PrearcDatabase.ready) { - sds = PrearcDatabase.getAllSessions(); + final UserI user = _provider.get(); + if (user == null) { + logger.warn("The user for running the session XML rebuilder process was not found. Aborting for now."); + return; } - } catch (SessionException e) { - logger.error("", e); - } catch (SQLException e) { - // Swallow this message so it doesn't fill the logs before the prearchive is initialized. - if (!e.getMessage().contains("relation \"xdat_search.prearchive\" does not exist")) { + logger.trace("Running prearc job as {}", user.getLogin()); + List<SessionData> sds = null; + long now = Calendar.getInstance().getTimeInMillis(); + try { + if (PrearcDatabase.ready) { + sds = PrearcDatabase.getAllSessions(); + } + } catch (SessionException e) { + logger.error("", e); + } catch (SQLException e) { + // Swallow this message so it doesn't fill the logs before the prearchive is initialized. + if (!e.getMessage().contains("relation \"xdat_search.prearchive\" does not exist")) { + logger.error("", e); + } + } catch (Exception e) { logger.error("", e); } - } catch (Exception e) { - logger.error("", e); - } - int updated = 0; - int total = 0; - if (sds != null && sds.size() > 0) { - for (final SessionData sessionData : sds) { - total++; - if (sessionData.getStatus().equals(PrearcUtils.PrearcStatus.RECEIVING) && !sessionData.getPreventAutoCommit() && !StringUtils.trimToEmpty(sessionData.getSource()).equals("applet")) { - try { - final File sessionDir = PrearcUtils.getPrearcSessionDir(user, sessionData.getProject(), sessionData.getTimestamp(), sessionData.getFolderName(), false); - final long then = sessionData.getLastBuiltDate().getTime(); - final double diff = diffInMinutes(then, now); - if (diff >= _interval && !PrearcUtils.isSessionReceiving(sessionData.getSessionDataTriple())) { - updated++; - try { - if (PrearcDatabase.setStatus(sessionData.getFolderName(), sessionData.getTimestamp(), sessionData.getProject(), PrearcUtils.PrearcStatus.QUEUED_BUILDING)) { - logger.debug("Creating JMS queue entry for {} to archive {}", user.getUsername(), sessionData.getExternalUrl()); - final PrearchiveOperationRequest request = new PrearchiveOperationRequest(user, sessionData, sessionDir, "Rebuild"); - XDAT.sendJmsRequest(_jmsTemplate, request); + int updated = 0; + int total = 0; + if (sds != null && sds.size() > 0) { + for (final SessionData sessionData : sds) { + total++; + if (sessionData.getStatus().equals(PrearcUtils.PrearcStatus.RECEIVING) && !sessionData.getPreventAutoCommit() && !StringUtils.trimToEmpty(sessionData.getSource()).equals("applet")) { + try { + final File sessionDir = PrearcUtils.getPrearcSessionDir(user, sessionData.getProject(), sessionData.getTimestamp(), sessionData.getFolderName(), false); + final long then = sessionData.getLastBuiltDate().getTime(); + final double diff = diffInMinutes(then, now); + if (diff >= _interval && !PrearcUtils.isSessionReceiving(sessionData.getSessionDataTriple())) { + updated++; + try { + if (PrearcDatabase.setStatus(sessionData.getFolderName(), sessionData.getTimestamp(), sessionData.getProject(), PrearcUtils.PrearcStatus.QUEUED_BUILDING)) { + logger.debug("Creating JMS queue entry for {} to archive {}", user.getUsername(), sessionData.getExternalUrl()); + final PrearchiveOperationRequest request = new PrearchiveOperationRequest(user, sessionData, sessionDir, "Rebuild"); + XDAT.sendJmsRequest(_jmsTemplate, request); + } + } catch (Exception exception) { + logger.error("Error when setting prearchive session status to QUEUED", exception); } - } catch (Exception exception) { - logger.error("Error when setting prearchive session status to QUEUED", exception); + } else if (diff >= (_interval * 10)) { + logger.error(String.format("Prearchive session locked for an abnormally large time within CACHE_DIR/prearc_locks/%1$s/%2$s/%3$s", sessionData.getProject(), sessionData.getTimestamp(), sessionData.getName())); } - } else if (diff >= (_interval * 10)) { - logger.error(String.format("Prearchive session locked for an abnormally large time within CACHE_DIR/prearc_locks/%1$s/%2$s/%3$s", sessionData.getProject(), sessionData.getTimestamp(), sessionData.getName())); + } catch (IOException e) { + final String message = String.format("An error occurred trying to write the session %s %s %s.", sessionData.getFolderName(), sessionData.getTimestamp(), sessionData.getProject()); + logger.error(message, e); + } catch (InvalidPermissionException e) { + final String message = String.format("A permissions error occurred trying to write the session %s %s %s.", sessionData.getFolderName(), sessionData.getTimestamp(), sessionData.getProject()); + logger.error(message, e); + } catch (Exception e) { + final String message = String.format("An unknown error occurred trying to write the session %s %s %s.", sessionData.getFolderName(), sessionData.getTimestamp(), sessionData.getProject()); + logger.error(message, e); } - } catch (IOException e) { - final String message = String.format("An error occurred trying to write the session %s %s %s.", sessionData.getFolderName(), sessionData.getTimestamp(), sessionData.getProject()); - logger.error(message, e); - } catch (InvalidPermissionException e) { - final String message = String.format("A permissions error occurred trying to write the session %s %s %s.", sessionData.getFolderName(), sessionData.getTimestamp(), sessionData.getProject()); - logger.error(message, e); - } catch (Exception e) { - final String message = String.format("An unknown error occurred trying to write the session %s %s %s.", sessionData.getFolderName(), sessionData.getTimestamp(), sessionData.getProject()); - logger.error(message, e); } } } + logger.info("Built {} of {}", updated, total); + } catch (final NrgServiceRuntimeException e) { + switch (e.getServiceError()) { + case UserServiceError: + logger.warn("The user for running the session XML rebuilder process could not be initialized. This probably means the system is still initializing. Check the database if this is not the case."); + break; + default: + throw e; + } } - logger.info("Built {} of {}", updated, total); } public static double diffInMinutes(long start, long end) { @@ -101,6 +126,9 @@ public class SessionXMLRebuilder implements Runnable { private static final Logger logger = LoggerFactory.getLogger(SessionXMLRebuilder.class); private final Provider<UserI> _provider; + private XnatAppInfo _appInfo; private final double _interval; private final JmsTemplate _jmsTemplate; + + private boolean _markedUninitialized = false; } diff --git a/src/main/java/org/nrg/xnat/helpers/prearchive/handlers/PrearchiveRebuildHandler.java b/src/main/java/org/nrg/xnat/helpers/prearchive/handlers/PrearchiveRebuildHandler.java index 937a10c0739fbe640a3a306a7b5a3f2823cf194f..95a5c6661b9abf26561e6aeec3dd5deff2375a37 100644 --- a/src/main/java/org/nrg/xnat/helpers/prearchive/handlers/PrearchiveRebuildHandler.java +++ b/src/main/java/org/nrg/xnat/helpers/prearchive/handlers/PrearchiveRebuildHandler.java @@ -7,6 +7,7 @@ import org.nrg.xdat.bean.reader.XDATXMLReader; import org.nrg.xnat.archive.FinishImageUpload; import org.nrg.xnat.helpers.prearchive.PrearcDatabase; import org.nrg.xnat.helpers.prearchive.PrearcUtils; +import org.nrg.xnat.helpers.prearchive.SessionData; import org.nrg.xnat.restlet.actions.PrearcImporterA; import org.nrg.xnat.services.messaging.prearchive.PrearchiveOperationRequest; import org.slf4j.Logger; @@ -38,42 +39,55 @@ public class PrearchiveRebuildHandler extends AbstractPrearchiveOperationHandler } } else if (PrearcDatabase.setStatus(getSessionData().getFolderName(), getSessionData().getTimestamp(), getSessionData().getProject(), PrearcUtils.PrearcStatus.BUILDING)) { PrearcDatabase.buildSession(getSessionDir(), getSessionData().getFolderName(), getSessionData().getTimestamp(), getSessionData().getProject(), getSessionData().getVisit(), getSessionData().getProtocol(), getSessionData().getTimeZone(), getSessionData().getSource()); - final boolean separatePetMr = PrearcUtils.isUnassigned(getSessionData()) ? PrearcUtils.shouldSeparatePetMr() : PrearcUtils.shouldSeparatePetMr(getSessionData().getProject()); - if (separatePetMr) { - if (_log.isDebugEnabled()) { - _log.debug("Found create separate PET and MR sessions setting for project {}, now working to separate that.", getSessionData().getProject()); - } - final File sessionXml = new File(getSessionDir() + ".xml"); - if (sessionXml.exists()) { + + // We need to check whether the session was updated to RECEIVING_INTERRUPT while the rebuild operation + // was happening. If that happened, that means more data started to arrive during the rebuild. If not, + // we'll proceed down the path where we check for session splits and autoarchive. If so, we'll just + // reset the status to RECEIVING and update the session timestamp. + final SessionData current = PrearcDatabase.getSession(getSessionData().getSessionDataTriple()); + if (current.getStatus() != PrearcUtils.PrearcStatus.RECEIVING_INTERRUPT) { + final boolean separatePetMr = PrearcUtils.isUnassigned(getSessionData()) ? PrearcUtils.shouldSeparatePetMr() : PrearcUtils.shouldSeparatePetMr(getSessionData().getProject()); + if (separatePetMr) { if (_log.isDebugEnabled()) { - _log.debug("Found the session XML in the file {}, processing.", sessionXml.getAbsolutePath()); + _log.debug("Found create separate PET and MR sessions setting for project {}, now working to separate that.", getSessionData().getProject()); } - final XnatImagesessiondataBean bean = (XnatImagesessiondataBean) new XDATXMLReader().parse(sessionXml); - if (bean instanceof XnatPetmrsessiondataBean) { + final File sessionXml = new File(getSessionDir() + ".xml"); + if (sessionXml.exists()) { if (_log.isDebugEnabled()) { - _log.debug("Found a PET/MR session XML in the file {} with the separate PET/MR flag set to true for the site or project, creating a new request to separate the session.", sessionXml.getAbsolutePath()); + _log.debug("Found the session XML in the file {}, processing.", sessionXml.getAbsolutePath()); } - PrearcUtils.resetStatus(getUser(), getSessionData().getProject(), getSessionData().getTimestamp(), getSessionData().getFolderName(), true); - final PrearchiveOperationRequest request = new PrearchiveOperationRequest(getUser(), getSessionData(), getSessionDir(), "Separate"); - XDAT.sendJmsRequest(request); - return; - } else if (_log.isDebugEnabled()) { - _log.debug("Found a session XML for a {} session in the file {}. Not PET/MR so not separating.", bean.getFullSchemaElementName(), sessionXml.getAbsolutePath()); + final XnatImagesessiondataBean bean = (XnatImagesessiondataBean) new XDATXMLReader().parse(sessionXml); + if (bean instanceof XnatPetmrsessiondataBean) { + if (_log.isDebugEnabled()) { + _log.debug("Found a PET/MR session XML in the file {} with the separate PET/MR flag set to true for the site or project, creating a new request to separate the session.", sessionXml.getAbsolutePath()); + } + PrearcUtils.resetStatus(getUser(), getSessionData().getProject(), getSessionData().getTimestamp(), getSessionData().getFolderName(), true); + final PrearchiveOperationRequest request = new PrearchiveOperationRequest(getUser(), getSessionData(), getSessionDir(), "Separate"); + XDAT.sendJmsRequest(request); + return; + } else if (_log.isDebugEnabled()) { + _log.debug("Found a session XML for a {} session in the file {}. Not PET/MR so not separating.", bean.getFullSchemaElementName(), sessionXml.getAbsolutePath()); + } + } else { + _log.warn("Tried to rebuild a session from the path {}, but that session XML doesn't exist.", sessionXml.getAbsolutePath()); } - } else { - _log.warn("Tried to rebuild a session from the path {}, but that session XML doesn't exist.", sessionXml.getAbsolutePath()); } - } - PrearcUtils.resetStatus(getUser(), getSessionData().getProject(), getSessionData().getTimestamp(), getSessionData().getFolderName(), true); + PrearcUtils.resetStatus(getUser(), getSessionData().getProject(), getSessionData().getTimestamp(), getSessionData().getFolderName(), true); - // we don't want to autoarchive a session that's just being rebuilt - // but we still want to autoarchive sessions that just came from RECEIVING STATE - final PrearcImporterA.PrearcSession session = new PrearcImporterA.PrearcSession(getSessionData().getProject(), getSessionData().getTimestamp(), getSessionData().getFolderName(), null, getUser()); - final FinishImageUpload uploader = new FinishImageUpload(null, getUser(), session, null, false, true, false); - if (receiving || !uploader.isAutoArchive()) { - _log.debug("Processing queue entry for {} in project {} to archive {}", getUser().getUsername(), getSessionData().getProject(), getSessionData().getExternalUrl()); - uploader.call(); + // we don't want to autoarchive a session that's just being rebuilt + // but we still want to autoarchive sessions that just came from RECEIVING STATE + final PrearcImporterA.PrearcSession session = new PrearcImporterA.PrearcSession(getSessionData().getProject(), getSessionData().getTimestamp(), getSessionData().getFolderName(), null, getUser()); + final FinishImageUpload uploader = new FinishImageUpload(null, getUser(), session, null, false, true, false); + if (receiving || !uploader.isAutoArchive()) { + _log.debug("Processing queue entry for {} in project {} to archive {}", getUser().getUsername(), getSessionData().getProject(), getSessionData().getExternalUrl()); + uploader.call(); + } + } else { + if (_log.isInfoEnabled()) { + _log.info("Found session " + getSessionData().getSessionDataTriple() + " in RECEIVING_INTERRUPT state, meaning that data began arriving while session was in an interruptable non-receiving state. No session split or autoarchive checks will be performed and session will be restored to RECEIVING state."); + } + PrearcDatabase.setStatus(getSessionData().getFolderName(), getSessionData().getTimestamp(), getSessionData().getProject(), PrearcUtils.PrearcStatus.RECEIVING); } } } catch (Exception e) { diff --git a/src/main/java/org/nrg/xnat/initialization/DatabaseConfig.java b/src/main/java/org/nrg/xnat/initialization/DatabaseConfig.java index 83b59d908feb34e04c5190357cf3748526e9e179..d4f2e81074eb65019a470b0ea81a4f75c669d0d6 100644 --- a/src/main/java/org/nrg/xnat/initialization/DatabaseConfig.java +++ b/src/main/java/org/nrg/xnat/initialization/DatabaseConfig.java @@ -13,7 +13,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; import org.springframework.jdbc.core.JdbcTemplate; -import javax.inject.Inject; import javax.sql.DataSource; import java.lang.reflect.InvocationTargetException; import java.util.Properties; @@ -34,8 +33,8 @@ public class DatabaseConfig { public static final String DEFAULT_DATASOURCE_MAX_IDLE = "10"; @Bean - public DataSource dataSource() throws NrgServiceException { - final Properties properties = Beans.getNamespacedProperties(_environment, "datasource", true); + public DataSource dataSource(final Environment environment) throws NrgServiceException { + final Properties properties = Beans.getNamespacedProperties(environment, "datasource", true); setDefaultDatasourceProperties(properties); final String dataSourceClassName = properties.getProperty("class"); try { @@ -57,8 +56,8 @@ public class DatabaseConfig { } @Bean - public JdbcTemplate jdbcTemplate() throws NrgServiceException { - return new JdbcTemplate(dataSource()); + public JdbcTemplate jdbcTemplate(final DataSource dataSource) throws NrgServiceException { + return new JdbcTemplate(dataSource); } private static Properties setDefaultDatasourceProperties(final Properties properties) { @@ -118,7 +117,4 @@ public class DatabaseConfig { } private static final Logger _log = LoggerFactory.getLogger(DatabaseConfig.class); - - @Inject - private Environment _environment; } diff --git a/src/main/java/org/nrg/xnat/initialization/InitializingTasksExecutor.java b/src/main/java/org/nrg/xnat/initialization/InitializingTasksExecutor.java index 64fbd2d1830a459e23b5f0c8c02ee852ad9c4e86..5fd693c38d6a45d6bbe9488a38acbecc20bb81d7 100644 --- a/src/main/java/org/nrg/xnat/initialization/InitializingTasksExecutor.java +++ b/src/main/java/org/nrg/xnat/initialization/InitializingTasksExecutor.java @@ -12,6 +12,12 @@ import java.util.List; @Component public class InitializingTasksExecutor { + @Autowired + @Lazy + public InitializingTasksExecutor(final List<InitializingTask> tasks) { + _tasks = tasks; + } + @EventListener public void executeOnContextRefresh(final ContextRefreshedEvent event) { if (_log.isDebugEnabled()) { @@ -32,7 +38,5 @@ public class InitializingTasksExecutor { private static final Logger _log = LoggerFactory.getLogger(InitializingTasksExecutor.class); - @Autowired - @Lazy - private List<InitializingTask> _tasks; + private final List<InitializingTask> _tasks; } diff --git a/src/main/java/org/nrg/xnat/initialization/PropertiesConfig.java b/src/main/java/org/nrg/xnat/initialization/PropertiesConfig.java index f6134038e051af0581c406d52b12a2f8cc53e431..b96165fcd8b03629ec3249e9d963176b042d1663 100644 --- a/src/main/java/org/nrg/xnat/initialization/PropertiesConfig.java +++ b/src/main/java/org/nrg/xnat/initialization/PropertiesConfig.java @@ -12,7 +12,6 @@ import org.springframework.context.annotation.PropertySources; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.env.Environment; -import javax.inject.Inject; import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; @@ -56,12 +55,12 @@ public class PropertiesConfig { } @Bean - public Path xnatHome() { + public Path xnatHome(final Environment environment) { if (_xnatHome == null) { // We just get the parent of the first folder in the list of configuration folders XNAT_HOME. This won't be // null because, if there are no valid configuration folders, the config folders method will have already // thrown an exception. - _xnatHome = configFolderPaths().get(0).getParent(); + _xnatHome = configFolderPaths(environment).get(0).getParent(); if (_log.isInfoEnabled()) { _log.info("Set path {} as the XNAT home folder.", _xnatHome); } @@ -70,22 +69,22 @@ public class PropertiesConfig { } @Bean - public List<String> configFilesLocations() { + public List<String> configFilesLocations(final Environment environment) { // The configuration service should be converted to use List<Path> instead of List<String> and this bean should // be deprecated and removed. if (_configFolderLocations.size() == 0) { - configFolderPaths(); + configFolderPaths(environment); } return _configFolderLocations; } @Bean - public List<Path> configFolderPaths() { + public List<Path> configFolderPaths(final Environment environment) { if (_configFolderPaths.size() == 0) { final Map<String, String> paths = new HashMap<>(); for (int index = 0; index < CONFIG_LOCATIONS.size(); index++) { paths.put(CONFIG_LOCATIONS.get(index), CONFIG_PATHS.get(index)); - final Path path = getConfigFolder(_environment, CONFIG_LOCATIONS.get(index), CONFIG_PATHS.get(index)); + final Path path = getConfigFolder(environment, CONFIG_LOCATIONS.get(index), CONFIG_PATHS.get(index)); if (path != null) { if (_log.isInfoEnabled()) { _log.info("Adding path {} to the list of available configuration folders.", path.toString()); @@ -98,7 +97,7 @@ public class PropertiesConfig { final StringBuilder writer = new StringBuilder("No XNAT home specified in any of the accepted locations:\n"); for (final String variable : paths.keySet()) { writer.append(" * "); - final String value = _environment.getProperty(variable); + final String value = environment.getProperty(variable); if (StringUtils.isBlank(value)) { writer.append(variable).append(": Not defined"); } else { @@ -173,7 +172,7 @@ public class PropertiesConfig { } final Path candidate = Paths.get(value, relative); - final File file = candidate.toFile(); + final File file = candidate.toFile(); if (file.exists()) { // If it's a directory... if (file.isDirectory()) { @@ -208,9 +207,6 @@ public class PropertiesConfig { private static final List<String> CONFIG_URLS = new ArrayList<>(); - @Inject - private Environment _environment; - private final List<Path> _configFolderPaths = new ArrayList<>(); private final List<String> _configFolderLocations = new ArrayList<>(); private Path _xnatHome; diff --git a/src/main/java/org/nrg/xnat/initialization/RootConfig.java b/src/main/java/org/nrg/xnat/initialization/RootConfig.java index bb960af64f969f878adeb1917371a9a07cdd6661..87bb0676675ed8b2548f452898e193ee5d626723 100644 --- a/src/main/java/org/nrg/xnat/initialization/RootConfig.java +++ b/src/main/java/org/nrg/xnat/initialization/RootConfig.java @@ -12,7 +12,6 @@ import org.nrg.framework.exceptions.NrgServiceException; import org.nrg.framework.services.ContextService; import org.nrg.framework.services.SerializerService; import org.nrg.prefs.beans.PreferenceBeanMixIn; -import org.nrg.xdat.preferences.InitializerSiteConfiguration; import org.nrg.xdat.preferences.SiteConfigPreferences; import org.nrg.xnat.configuration.ApplicationConfig; import org.nrg.xnat.helpers.prearchive.PrearcConfig; @@ -41,7 +40,7 @@ import java.util.Map; * for standard XNAT components should be added in the {@link ApplicationConfig application configuration class}. */ @Configuration -@Import({PropertiesConfig.class, DatabaseConfig.class, SecurityConfig.class}) +@Import({PropertiesConfig.class, DatabaseConfig.class, SecurityConfig.class, ApplicationConfig.class}) public class RootConfig { @Bean public XnatAppInfo appInfo(final ServletContext context, final JdbcTemplate template) throws IOException { @@ -49,12 +48,7 @@ public class RootConfig { } @Bean - public InitializerSiteConfiguration initializerSiteConfiguration() { - return new InitializerSiteConfiguration(); - } - - @Bean - public ContextService rootContextService() throws NrgServiceException { + public ContextService contextService() throws NrgServiceException { return ContextService.getInstance(); } diff --git a/src/main/java/org/nrg/xnat/initialization/SecurityConfig.java b/src/main/java/org/nrg/xnat/initialization/SecurityConfig.java index 6bca03b5a2bf491b32dc29a83aceb0a328c3331d..6bd28e333c9c895c28d22e375aabf018e4f5f8e6 100644 --- a/src/main/java/org/nrg/xnat/initialization/SecurityConfig.java +++ b/src/main/java/org/nrg/xnat/initialization/SecurityConfig.java @@ -5,7 +5,9 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import org.nrg.config.exceptions.SiteConfigurationException; import org.nrg.framework.services.SerializerService; -import org.nrg.xdat.preferences.InitializerSiteConfiguration; +import org.nrg.xdat.preferences.SiteConfigPreferences; +import org.nrg.xdat.services.AliasTokenService; +import org.nrg.xdat.services.XdatUserAuthService; import org.nrg.xnat.security.*; import org.nrg.xnat.security.alias.AliasTokenAuthenticationProvider; import org.nrg.xnat.security.config.AuthenticationProviderAggregator; @@ -13,19 +15,23 @@ import org.nrg.xnat.security.config.AuthenticationProviderConfigurator; import org.nrg.xnat.security.config.DatabaseAuthenticationProviderConfigurator; import org.nrg.xnat.security.config.LdapAuthenticationProviderConfigurator; import org.nrg.xnat.security.userdetailsservices.XnatDatabaseUserDetailsService; -import org.springframework.beans.factory.annotation.Autowired; +import org.nrg.xnat.services.XnatAppInfo; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportResource; -import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Primary; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.vote.AuthenticatedVoter; import org.springframework.security.access.vote.RoleVoter; import org.springframework.security.access.vote.UnanimousBased; +import org.springframework.security.authentication.AnonymousAuthenticationProvider; +import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.core.session.SessionRegistry; import org.springframework.security.core.session.SessionRegistryImpl; import org.springframework.security.web.AuthenticationEntryPoint; @@ -33,7 +39,6 @@ import org.springframework.security.web.access.channel.ChannelDecisionManagerImp import org.springframework.security.web.access.channel.InsecureChannelProcessor; import org.springframework.security.web.access.channel.SecureChannelProcessor; import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.logout.LogoutFilter; import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; import org.springframework.security.web.authentication.session.*; @@ -45,6 +50,7 @@ import java.io.InputStream; import java.util.*; @Configuration +@EnableWebSecurity @ImportResource("WEB-INF/conf/xnat-security.xml") public class SecurityConfig { @Bean @@ -68,8 +74,8 @@ public class SecurityConfig { } @Bean - public XnatAuthenticationEntryPoint loginUrlAuthenticationEntryPoint() { - final XnatAuthenticationEntryPoint entryPoint = new XnatAuthenticationEntryPoint("/app/template/Login.vm", _configuration); + public XnatAuthenticationEntryPoint loginUrlAuthenticationEntryPoint(final SiteConfigPreferences preferences) { + final XnatAuthenticationEntryPoint entryPoint = new XnatAuthenticationEntryPoint("/app/template/Login.vm", preferences); entryPoint.setDataPaths(Arrays.asList("/xapi/**", "/data/**", "/REST/**", "/fs/**")); entryPoint.setInteractiveAgents(Arrays.asList(".*MSIE.*", ".*Mozilla.*", ".*AppleWebKit.*", ".*Opera.*")); return entryPoint; @@ -86,11 +92,12 @@ public class SecurityConfig { } @Bean - public CompositeSessionAuthenticationStrategy sas(final SessionRegistry sessionRegistry) throws SiteConfigurationException { + @Primary + public CompositeSessionAuthenticationStrategy sas(final SessionRegistry sessionRegistry, final SiteConfigPreferences preferences) throws SiteConfigurationException { ArrayList<SessionAuthenticationStrategy> authStrategies = new ArrayList<>(); final ConcurrentSessionControlAuthenticationStrategy strategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry); - strategy.setMaximumSessions(_configuration.getConcurrentMaxSessions()); + strategy.setMaximumSessions(preferences.getConcurrentMaxSessions()); strategy.setExceptionIfMaximumExceeded(true); authStrategies.add(strategy); @@ -104,24 +111,24 @@ public class SecurityConfig { } @Bean - public LogoutFilter logoutFilter() { + public LogoutFilter logoutFilter(final SessionRegistry sessionRegistry) { final XnatLogoutSuccessHandler logoutSuccessHandler = new XnatLogoutSuccessHandler(); logoutSuccessHandler.setOpenXnatLogoutSuccessUrl("/"); logoutSuccessHandler.setSecuredXnatLogoutSuccessUrl("/app/template/Login.vm"); final SecurityContextLogoutHandler securityContextLogoutHandler = new SecurityContextLogoutHandler(); securityContextLogoutHandler.setInvalidateHttpSession(true); - final XnatLogoutHandler xnatLogoutHandler = new XnatLogoutHandler(); - final LogoutFilter filter = new LogoutFilter(logoutSuccessHandler, securityContextLogoutHandler, xnatLogoutHandler); + final XnatLogoutHandler xnatLogoutHandler = new XnatLogoutHandler(sessionRegistry); + final LogoutFilter filter = new LogoutFilter(logoutSuccessHandler, securityContextLogoutHandler, xnatLogoutHandler); filter.setFilterProcessesUrl("/app/action/LogoutUser"); return filter; } @Bean - public FilterSecurityInterceptorBeanPostProcessor filterSecurityInterceptorBeanPostProcessor() throws IOException { + public FilterSecurityInterceptorBeanPostProcessor filterSecurityInterceptorBeanPostProcessor(final SerializerService serializer, final SiteConfigPreferences preferences) throws IOException { final Resource resource = RESOURCE_LOADER.getResource("classpath:META-INF/xnat/security/configured-urls.yaml"); try (final InputStream inputStream = resource.getInputStream()) { - final HashMap<String, ArrayList<String>> urlMap = _serializer.deserializeYaml(inputStream, TYPE_REFERENCE); - final FilterSecurityInterceptorBeanPostProcessor postProcessor = new FilterSecurityInterceptorBeanPostProcessor(); + final HashMap<String, ArrayList<String>> urlMap = serializer.deserializeYaml(inputStream, TYPE_REFERENCE); + final FilterSecurityInterceptorBeanPostProcessor postProcessor = new FilterSecurityInterceptorBeanPostProcessor(preferences); postProcessor.setOpenUrls(urlMap.get("openUrls")); postProcessor.setAdminUrls(urlMap.get("adminUrls")); return postProcessor; @@ -129,28 +136,28 @@ public class SecurityConfig { } @Bean - public TranslatingChannelProcessingFilter channelProcessingFilter() throws SiteConfigurationException { + public TranslatingChannelProcessingFilter channelProcessingFilter(final SiteConfigPreferences preferences) throws SiteConfigurationException { final ChannelDecisionManagerImpl decisionManager = new ChannelDecisionManagerImpl(); decisionManager.setChannelProcessors(Arrays.asList(new SecureChannelProcessor(), new InsecureChannelProcessor())); final TranslatingChannelProcessingFilter filter = new TranslatingChannelProcessingFilter(); filter.setChannelDecisionManager(decisionManager); - filter.setRequiredChannel(_configuration.getSecurityChannel()); + filter.setRequiredChannel(preferences.getSecurityChannel()); return filter; } @Bean - public AliasTokenAuthenticationProvider aliasTokenAuthenticationProvider() { - return new AliasTokenAuthenticationProvider(); + public AliasTokenAuthenticationProvider aliasTokenAuthenticationProvider(final AliasTokenService aliasTokenService, final XdatUserAuthService userAuthService) { + return new AliasTokenAuthenticationProvider(aliasTokenService, userAuthService); } @Bean - public DatabaseAuthenticationProviderConfigurator dbConfigurator() { - return new DatabaseAuthenticationProviderConfigurator(); + public DatabaseAuthenticationProviderConfigurator dbConfigurator(final XnatDatabaseUserDetailsService userDetailsService, final SiteConfigPreferences preferences) { + return new DatabaseAuthenticationProviderConfigurator(userDetailsService, preferences); } @Bean - public LdapAuthenticationProviderConfigurator ldapConfigurator() { - return new LdapAuthenticationProviderConfigurator(); + public LdapAuthenticationProviderConfigurator ldapConfigurator(final XdatUserAuthService service, final SiteConfigPreferences preferences) { + return new LdapAuthenticationProviderConfigurator(service, preferences); } @Bean @@ -163,54 +170,44 @@ public class SecurityConfig { return new AuthenticationProviderAggregator(providers, configuratorMap); } - @Bean(name = {"org.springframework.security.authenticationManager", "customAuthenticationManager"}) - public XnatProviderManager customAuthenticationManager(final AuthenticationProviderAggregator aggregator) { - return new XnatProviderManager(aggregator); + @Bean + @Primary + public XnatProviderManager customAuthenticationManager(final AuthenticationProviderAggregator aggregator, final XdatUserAuthService userAuthService, @SuppressWarnings("SpringJavaAutowiringInspection") final AnonymousAuthenticationProvider anonymousAuthenticationProvider, final DataSource dataSource) { + return new XnatProviderManager(aggregator, userAuthService, anonymousAuthenticationProvider, dataSource); } @Bean - public XnatAuthenticationFilter customAuthenticationFilter(final XnatProviderManager providerManager, - final AuthenticationSuccessHandler successHandler, - final AuthenticationFailureHandler failureHandler, - final SessionAuthenticationStrategy sas) { - final XnatAuthenticationFilter filter = new XnatAuthenticationFilter(); - filter.setAuthenticationManager(providerManager); - filter.setAuthenticationSuccessHandler(successHandler); - filter.setAuthenticationFailureHandler(failureHandler); - filter.setSessionAuthenticationStrategy(sas); - return filter; + public XnatAuthenticationFilter customAuthenticationFilter() { + return new XnatAuthenticationFilter(); } @Bean - public XnatBasicAuthenticationFilter customBasicAuthenticationFilter(final XnatProviderManager providerManager, - final AuthenticationEntryPoint entryPoint, - final SessionAuthenticationStrategy sas) { - final XnatBasicAuthenticationFilter filter = new XnatBasicAuthenticationFilter(providerManager, entryPoint); - filter.setSessionAuthenticationStrategy(sas); - return filter; + public XnatBasicAuthenticationFilter customBasicAuthenticationFilter(final AuthenticationManager authenticationManager, + final AuthenticationEntryPoint entryPoint) { + return new XnatBasicAuthenticationFilter(authenticationManager, entryPoint); } @Bean - public XnatExpiredPasswordFilter expiredPasswordFilter() { - final XnatExpiredPasswordFilter filter = new XnatExpiredPasswordFilter(); - filter.setChangePasswordPath("/app/template/XDATScreen_UpdateUser.vm"); - filter.setChangePasswordDestination("/app/action/ModifyPassword"); - filter.setLogoutDestination("/app/action/LogoutUser"); - filter.setLoginPath("/app/template/Login.vm"); - filter.setLoginDestination("/app/action/XDATLoginUser"); - filter.setInactiveAccountPath("/app/template/InactiveAccount.vm"); - filter.setInactiveAccountDestination("/app/action/XnatInactiveAccount"); - filter.setEmailVerificationPath("/app/template/VerifyEmail.vm"); - filter.setEmailVerificationDestination("/data/services/sendEmailVerification"); - return filter; + public XnatExpiredPasswordFilter expiredPasswordFilter(final SiteConfigPreferences preferences, final JdbcTemplate jdbcTemplate, final AliasTokenService aliasTokenService) { + return new XnatExpiredPasswordFilter(preferences, jdbcTemplate, aliasTokenService) {{ + setChangePasswordPath("/app/template/XDATScreen_UpdateUser.vm"); + setChangePasswordDestination("/app/action/ModifyPassword"); + setLogoutDestination("/app/action/LogoutUser"); + setLoginPath("/app/template/Login.vm"); + setLoginDestination("/app/action/XDATLoginUser"); + setInactiveAccountPath("/app/template/InactiveAccount.vm"); + setInactiveAccountDestination("/app/action/XnatInactiveAccount"); + setEmailVerificationPath("/app/template/VerifyEmail.vm"); + setEmailVerificationDestination("/data/services/sendEmailVerification"); + }}; } @Bean - public XnatInitCheckFilter xnatInitCheckFilter() throws IOException { + public XnatInitCheckFilter xnatInitCheckFilter(final SerializerService serializer, final XnatAppInfo appInfo) throws IOException { final Resource resource = RESOURCE_LOADER.getResource("classpath:META-INF/xnat/security/initialization-urls.yaml"); try (final InputStream inputStream = resource.getInputStream()) { - final XnatInitCheckFilter filter = new XnatInitCheckFilter(); - final JsonNode paths = _serializer.deserializeYaml(inputStream); + final XnatInitCheckFilter filter = new XnatInitCheckFilter(appInfo); + final JsonNode paths = serializer.deserializeYaml(inputStream); filter.setConfigurationPath(paths.get("configPath").asText()); filter.setNonAdminErrorPath(paths.get("nonAdminErrorPath").asText()); filter.setInitializationPaths(nodeToList(paths.get("initPaths"))); @@ -220,10 +217,8 @@ public class SecurityConfig { } @Bean - public XnatDatabaseUserDetailsService customDatabaseService(final DataSource dataSource) { - final XnatDatabaseUserDetailsService service = new XnatDatabaseUserDetailsService(); - service.setDataSource(dataSource); - return service; + public XnatDatabaseUserDetailsService customDatabaseService(final XdatUserAuthService userAuthService, final DataSource dataSource) { + return new XnatDatabaseUserDetailsService(userAuthService, dataSource); } protected List<String> nodeToList(final JsonNode node) { @@ -244,12 +239,4 @@ public class SecurityConfig { private static final ResourceLoader RESOURCE_LOADER = new DefaultResourceLoader(); private static final TypeReference<HashMap<String, ArrayList<String>>> TYPE_REFERENCE = new TypeReference<HashMap<String, ArrayList<String>>>() { }; - - @Autowired - @Lazy - private InitializerSiteConfiguration _configuration; - - @Autowired - @Lazy - private SerializerService _serializer; } \ No newline at end of file diff --git a/src/main/java/org/nrg/xnat/initialization/XnatWebAppInitializer.java b/src/main/java/org/nrg/xnat/initialization/XnatWebAppInitializer.java index fd2a937e07410c87cfe5321f7ea64571731b0bb3..a6ac86737507390451a2fad9e4cf9ed39aaca940 100644 --- a/src/main/java/org/nrg/xnat/initialization/XnatWebAppInitializer.java +++ b/src/main/java/org/nrg/xnat/initialization/XnatWebAppInitializer.java @@ -9,7 +9,6 @@ import org.nrg.framework.exceptions.NrgServiceRuntimeException; import org.nrg.framework.processors.XnatPluginBean; import org.nrg.xdat.servlet.XDATAjaxServlet; import org.nrg.xdat.servlet.XDATServlet; -import org.nrg.xnat.configuration.ApplicationConfig; import org.nrg.xnat.restlet.servlet.XNATRestletServlet; import org.nrg.xnat.restlet.util.UpdateExpirationCookie; import org.nrg.xnat.security.XnatSessionEventPublisher; @@ -70,15 +69,15 @@ public class XnatWebAppInitializer extends AbstractAnnotationConfigDispatcherSer @Override protected Class<?>[] getRootConfigClasses() { - return new Class<?>[]{RootConfig.class}; + final List<Class<?>> configClasses = new ArrayList<>(); + configClasses.add(RootConfig.class); + configClasses.addAll(getPluginConfigs()); + return configClasses.toArray(new Class[configClasses.size()]); } @Override protected Class<?>[] getServletConfigClasses() { - final List<Class<?>> configClasses = new ArrayList<>(); - configClasses.add(ApplicationConfig.class); - configClasses.addAll(getPluginConfigs()); - return configClasses.toArray(new Class[configClasses.size()]); + return new Class[0]; } @Override @@ -118,6 +117,9 @@ public class XnatWebAppInitializer extends AbstractAnnotationConfigDispatcherSer final List<Class<?>> configs = new ArrayList<>(); try { for (final XnatPluginBean plugin : XnatPluginBean.findAllXnatPluginBeans()) { + if (_log.isInfoEnabled()) { + _log.info("Found plugin {} {}: {}", plugin.getId(), plugin.getName(), plugin.getDescription()); + } configs.add(Class.forName(plugin.getPluginClass())); } } catch (IOException e) { @@ -126,6 +128,10 @@ public class XnatWebAppInitializer extends AbstractAnnotationConfigDispatcherSer _log.error("Did not find a class specified in a plugin definition.", e); } + if (_log.isInfoEnabled()) { + _log.info("Found a total of {} plugins", configs.size()); + } + return configs; } diff --git a/src/main/java/org/nrg/xnat/initialization/tasks/EncryptXnatPasswords.java b/src/main/java/org/nrg/xnat/initialization/tasks/EncryptXnatPasswords.java index f958eb6e0383b455e1002e09a5e1ab9c18ea071a..fbc9f06307285389fe59adca04a19ef67beb7b47 100644 --- a/src/main/java/org/nrg/xnat/initialization/tasks/EncryptXnatPasswords.java +++ b/src/main/java/org/nrg/xnat/initialization/tasks/EncryptXnatPasswords.java @@ -11,8 +11,8 @@ package org.nrg.xnat.initialization.tasks; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.BadSqlGrammarException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.security.authentication.encoding.ShaPasswordEncoder; @@ -26,6 +26,12 @@ import java.util.Map; @Component public class EncryptXnatPasswords extends AbstractInitializingTask { + @Autowired + public EncryptXnatPasswords(final JdbcTemplate template) { + super(); + _template = template; + } + @Override public String getTaskName() { return "Encrypt XNAT passwords"; @@ -51,6 +57,8 @@ public class EncryptXnatPasswords extends AbstractInitializingTask { } complete(); + } catch (BadSqlGrammarException e) { + logger.info("Unable to execute user table password encryption, maybe the table doesn't exist yet?", e); } catch (Exception e) { logger.error("", e); } @@ -80,7 +88,5 @@ public class EncryptXnatPasswords extends AbstractInitializingTask { private static Logger logger = LoggerFactory.getLogger(EncryptXnatPasswords.class); - @Autowired - @Lazy - private JdbcTemplate _template; + private final JdbcTemplate _template; } diff --git a/src/main/java/org/nrg/xnat/initialization/tasks/GetSiteWideAnonScript.java b/src/main/java/org/nrg/xnat/initialization/tasks/GetSiteWideAnonScript.java index eae59e036967b4467f0bb6efdc127aba7a740a4d..20ca2f3746db51109dae6c7d87b8e60ecf7ea600 100644 --- a/src/main/java/org/nrg/xnat/initialization/tasks/GetSiteWideAnonScript.java +++ b/src/main/java/org/nrg/xnat/initialization/tasks/GetSiteWideAnonScript.java @@ -8,11 +8,18 @@ import org.nrg.xnat.helpers.merge.AnonUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; +import java.io.FileNotFoundException; + @Component public class GetSiteWideAnonScript extends AbstractInitializingTask { + @Autowired + public GetSiteWideAnonScript(final SiteConfigPreferences preferences) { + super(); + _preferences = preferences; + } + @Override public String getTaskName() { return "Get site-wide anon script"; @@ -25,7 +32,7 @@ public class GetSiteWideAnonScript extends AbstractInitializingTask { final Configuration initConfig = AnonUtils.getService().getScript(path, null); if (initConfig == null) { _log.info("Creating Script Table."); - final String siteWideScript = FileUtils.readFileToString(AnonUtils.getDefaultScript()); + final String siteWideScript = AnonUtils.getDefaultScript(); final String adminUser = _preferences.getReceivedFileUser(); if (adminUser != null) { AnonUtils.getService().setSiteWideScript(adminUser, path, siteWideScript); @@ -35,6 +42,8 @@ public class GetSiteWideAnonScript extends AbstractInitializingTask { } // there is a default site-wide script, so nothing to do here for the else. complete(); + } catch (FileNotFoundException e) { + _log.info("Couldn't find default anonymization script, waiting", e); } catch (Throwable e) { _log.error("Unable to either find or initialize script database", e); } @@ -42,7 +51,5 @@ public class GetSiteWideAnonScript extends AbstractInitializingTask { private static final Logger _log = LoggerFactory.getLogger(GetSiteWideAnonScript.class); - @Autowired - @Lazy - private SiteConfigPreferences _preferences; + private final SiteConfigPreferences _preferences; } diff --git a/src/main/java/org/nrg/xnat/initialization/tasks/GetSiteWidePETTracerList.java b/src/main/java/org/nrg/xnat/initialization/tasks/GetSiteWidePETTracerList.java index 4942dae0ab4b56555078b6d3c88c59f6cf9cf1c0..d2dd78680556b789ec4723949ca361b49e4d7fe5 100644 --- a/src/main/java/org/nrg/xnat/initialization/tasks/GetSiteWidePETTracerList.java +++ b/src/main/java/org/nrg/xnat/initialization/tasks/GetSiteWidePETTracerList.java @@ -1,17 +1,21 @@ package org.nrg.xnat.initialization.tasks; -import org.apache.commons.io.FileUtils; import org.nrg.config.entities.Configuration; -import org.nrg.xdat.security.helpers.Roles; -import org.nrg.xdat.security.helpers.Users; -import org.nrg.xft.security.UserI; import org.nrg.xnat.services.PETTracerUtils; +import org.nrg.xnat.utils.XnatUserProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class GetSiteWidePETTracerList extends AbstractInitializingTask { + @Autowired + public GetSiteWidePETTracerList(final XnatUserProvider primaryAdminUserProvider, final PETTracerUtils petTracerUtils) { + _adminUsername = primaryAdminUserProvider.getLogin(); + _petTracerUtils = petTracerUtils; + } + @Override public String getTaskName() { return "Get site-wide PET tracer list"; @@ -21,33 +25,19 @@ public class GetSiteWidePETTracerList extends AbstractInitializingTask { public void run() { try { final String path = PETTracerUtils.buildScriptPath(PETTracerUtils.ResourceScope.SITE_WIDE, ""); - final Configuration configuration = PETTracerUtils.getService().getTracerList(path, null); + final Configuration configuration = _petTracerUtils.getTracerList(path, null); if (configuration == null) { _log.info("Creating PET Tracer List."); - final String siteWide = FileUtils.readFileToString(PETTracerUtils.getDefaultTracerList()); - final UserI adminUser = getAdminUser(); - if (adminUser != null) { - PETTracerUtils.getService().setSiteWideTracerList(adminUser.getUsername(), path, siteWide); - } else { - throw new Exception("Site administrator not found."); - } + final String siteWide = PETTracerUtils.getDefaultTracerList(); + _petTracerUtils.setSiteWideTracerList(_adminUsername, path, siteWide); } - // there is a default site-wide tracer list, so nothing to do here for the else. - complete(); - } catch (Throwable e){ + } catch (Throwable e) { _log.error("Unable to either find or initialize the PET tracer list.", e); } } - private UserI getAdminUser() throws Exception { - for (String login : Users.getAllLogins()) { - final UserI user = Users.getUser(login); - if (Roles.isSiteAdmin(user)) { - return user; - } - } - return null; - } - private static final Logger _log = LoggerFactory.getLogger(GetSiteWidePETTracerList.class); + + private final String _adminUsername; + private final PETTracerUtils _petTracerUtils; } diff --git a/src/main/java/org/nrg/xnat/initialization/tasks/SystemPathVerification.java b/src/main/java/org/nrg/xnat/initialization/tasks/SystemPathVerification.java new file mode 100644 index 0000000000000000000000000000000000000000..ecdf887e4849955beb8414fc2ff676af08fc95d8 --- /dev/null +++ b/src/main/java/org/nrg/xnat/initialization/tasks/SystemPathVerification.java @@ -0,0 +1,146 @@ +/* + * org.nrg.xnat.initialization.tasks.SystemPathVerification + * XNAT http://www.xnat.org + * Copyright (c) 2016, Washington University School of Medicine + * All Rights Reserved + * + * Released under the Simplified BSD. + * + * Created: + * Author: Justin Cleveland (clevelandj@wustl.edu) + */ +package org.nrg.xnat.initialization.tasks; + +import org.nrg.mail.services.MailService; +import org.nrg.xdat.preferences.SiteConfigPreferences; +import org.nrg.xnat.services.XnatAppInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.ResultSetExtractor; +import org.springframework.stereotype.Component; + +import javax.mail.MessagingException; +import java.io.File; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +@Component +public class SystemPathVerification extends AbstractInitializingTask { + @Autowired + public SystemPathVerification(final JdbcTemplate template, final MailService mailService, final SiteConfigPreferences config, final XnatAppInfo appInfo) { + _template = template; + _mailService = mailService; + _config = config; + _appInfo = appInfo; + } + + @Override + public String getTaskName() { + return "System Path Verification"; + } + + private final static ArrayList<String> pathErrors = new ArrayList<>(); + public static String pathErrorWarning = null; + + @Override + public void run() { + if (_appInfo.isInitialized()) { + try { + validatePath(_config.getArchivePath(), "Archive", true); + validatePath(_config.getCachePath(), "Archive", false); + validatePath(_config.getPipelinePath(), "Archive", false); + validatePath(_config.getPrearchivePath(), "Archive", false); + + final ProjectExtractor pe = new ProjectExtractor(); + final Map<String, String> projects = _template.query("SELECT id, name FROM xnat_projectdata", pe); + if (pathErrors.size() > 0) { + // Send warning email to admin and issue browser notification + notifyOfPathErrors(projects.size()); + } else { + _config.setPathErrorWarning(""); + } + complete(); + } catch (SQLException e) { + logger.error("An error occurred trying to retrieve the values for the system paths.", e); + } + } + } + + private boolean validatePath(final String path, final String displayName, final boolean checkForFiles) throws SQLException { + File filePath = new File(path); + if (!filePath.exists()) { + pathErrors.add(displayName + " path \"" + path + "\" does not exist."); + return false; + } else if (!filePath.isDirectory()) { + pathErrors.add(displayName + " path \"" + path + "\" is not a directory."); + return false; + } else if (checkForFiles) { + File[] files = filePath.listFiles(); + final String noFiles = displayName + " files do not exist under \"" + path + "\"."; + if (files == null) { + pathErrors.add(noFiles); + return false; + } + if (files.length < 1) { + pathErrors.add(noFiles); + return false; + } + } + return true; + } + + private static class ProjectExtractor implements ResultSetExtractor<Map<String, String>> { + @Override + public Map<String, String> extractData(final ResultSet results) throws SQLException, DataAccessException { + final Map<String, String> projects = new HashMap<>(); + while (results.next()) { + projects.put(results.getString(1), results.getString(2)); + } + return projects; + } + } + + private void notifyOfPathErrors(int numProjects) { + if (numProjects > 0) { + int i = 1; + String adminEmail = _config.getAdminEmail(); + String sysName = _config.getSiteId(); + String emailSubj = sysName + " " + this.getTaskName() + " Failure"; + StringBuilder sb = new StringBuilder(); + String singPlurl = " has"; + if (numProjects > 1) { + singPlurl = "s have"; + } + sb.append("The following system path error"); + sb.append(singPlurl); + sb.append(" been discovered:"); + for (String err : pathErrors) { + sb.append("\n\t"); + sb.append(i++); + sb.append(". "); + sb.append(err); + } + _config.setPathErrorWarning(sb.toString().replace("\n", "<br>")); + pathErrorWarning = sb.insert(0, emailSubj + ": ").toString(); + logger.error(pathErrorWarning); + try { + _mailService.sendHtmlMessage(adminEmail, adminEmail, emailSubj, pathErrorWarning); + } catch (MessagingException e) { + logger.error("", e); + } + } + } + + private static Logger logger = LoggerFactory.getLogger(SystemPathVerification.class); + + private final JdbcTemplate _template; + private final MailService _mailService; + private final SiteConfigPreferences _config; + private final XnatAppInfo _appInfo; +} diff --git a/src/main/java/org/nrg/xnat/initialization/tasks/UpdateNewSecureDefinitions.java b/src/main/java/org/nrg/xnat/initialization/tasks/UpdateNewSecureDefinitions.java index 357ee715e6019d25c692eb9503536d4bf9e6505c..b05c6f06c8397cc0a900a927e7347fa5dd1c166d 100644 --- a/src/main/java/org/nrg/xnat/initialization/tasks/UpdateNewSecureDefinitions.java +++ b/src/main/java/org/nrg/xnat/initialization/tasks/UpdateNewSecureDefinitions.java @@ -5,11 +5,16 @@ import org.nrg.xdat.security.services.FeatureRepositoryServiceI; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; @Component public class UpdateNewSecureDefinitions extends AbstractInitializingTask { + @Autowired + public UpdateNewSecureDefinitions(final FeatureRepositoryServiceI featureRepositoryService) { + super(); + _featureRepositoryService = featureRepositoryService; + } + @Override public String getTaskName() { return "Update new secure definitions"; @@ -30,7 +35,5 @@ public class UpdateNewSecureDefinitions extends AbstractInitializingTask { private static final Logger _log = LoggerFactory.getLogger(UpdateNewSecureDefinitions.class); - @Autowired - @Lazy - private FeatureRepositoryServiceI _featureRepositoryService; + private final FeatureRepositoryServiceI _featureRepositoryService; } diff --git a/src/main/java/org/nrg/xnat/initialization/tasks/UpdateUserAuthTable.java b/src/main/java/org/nrg/xnat/initialization/tasks/UpdateUserAuthTable.java index fd4cde9032860bee69441b03b978c07baca325b4..379aef85cfae8af0b77569be5abf852ae918ba37 100644 --- a/src/main/java/org/nrg/xnat/initialization/tasks/UpdateUserAuthTable.java +++ b/src/main/java/org/nrg/xnat/initialization/tasks/UpdateUserAuthTable.java @@ -5,7 +5,7 @@ import org.nrg.xdat.services.XdatUserAuthService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; +import org.springframework.jdbc.BadSqlGrammarException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.security.core.authority.AuthorityUtils; @@ -15,8 +15,22 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; +/** + * Adds users from /old xdat_user table to new user authentication table if they are not already there. New local + * database users now get added to both automatically, but this is necessary so that those who upgrade from an earlier + * version still have their users be able to log in. Password expiry times are also added so that pre-existing users + * still have their passwords expire. + */ +@SuppressWarnings("SqlDialectInspection") @Component public class UpdateUserAuthTable extends AbstractInitializingTask { + @Autowired + public UpdateUserAuthTable(final JdbcTemplate template, final XdatUserAuthService xdatUserAuthService) { + super(); + _template = template; + _xdatUserAuthService = xdatUserAuthService; + } + @Override public String getTaskName() { return "Update the user authentication table"; @@ -24,39 +38,34 @@ public class UpdateUserAuthTable extends AbstractInitializingTask { @Override public void run() { - /** - * Adds users from /old xdat_user table to new user authentication table if they are not already there. New local database users now get added to both automatically, but this is necessary - * so that those who upgrade from an earlier version will still have their users be able to log in. Password expiry times are also added so that pre-existing users still have their passwords expire. - */ - final List<XdatUserAuth> unmapped = _template.query("SELECT login, enabled FROM xdat_user WHERE login NOT IN (SELECT xdat_username FROM xhbm_xdat_user_auth)", new RowMapper<XdatUserAuth>() { - @Override - public XdatUserAuth mapRow(final ResultSet resultSet, final int i) throws SQLException { - final String login = resultSet.getString("login"); - final boolean enabled = resultSet.getInt("enabled") == 1; + try { + final List<XdatUserAuth> unmapped = _template.query("SELECT login, enabled FROM xdat_user WHERE login NOT IN (SELECT xdat_username FROM xhbm_xdat_user_auth)", new RowMapper<XdatUserAuth>() { + @Override + public XdatUserAuth mapRow(final ResultSet resultSet, final int i) throws SQLException { + final String login = resultSet.getString("login"); + final boolean enabled = resultSet.getInt("enabled") == 1; + if (_log.isDebugEnabled()) { + _log.debug("Creating new user auth object for user {}, authentication is {}", login, enabled ? "enabled" : "disabled"); + } + return new XdatUserAuth(login, XdatUserAuthService.LOCALDB, enabled, true, true, true, AuthorityUtils.NO_AUTHORITIES, login, 0); + } + }); + for (XdatUserAuth userAuth : unmapped) { if (_log.isDebugEnabled()) { - _log.debug("Creating new user auth object for user {}, authentication is {}", login, enabled ? "enabled" : "disabled"); + _log.debug("Persisting user auth object for user {}", userAuth.getXdatUsername()); } - return new XdatUserAuth(login, XdatUserAuthService.LOCALDB, enabled, true, true, true, AuthorityUtils.NO_AUTHORITIES, login, 0); + _xdatUserAuthService.create(userAuth); } - }); - for (XdatUserAuth userAuth : unmapped) { - if (_log.isDebugEnabled()) { - _log.debug("Persisting user auth object for user {}", userAuth.getXdatUsername()); - } - _xdatUserAuthService.create(userAuth); + _log.debug("Updating the user auth table to set password updated to the current time for local users"); + _template.execute("UPDATE xhbm_xdat_user_auth SET password_updated=current_timestamp WHERE auth_method='" + XdatUserAuthService.LOCALDB + "' AND password_updated IS NULL"); + complete(); + } catch (BadSqlGrammarException e) { + _log.info("Unable to execute user auth table update, maybe the table doesn't exist yet?", e); } - _log.debug("Updating the user auth table to set password updated to the current time for local users"); - _template.execute("UPDATE xhbm_xdat_user_auth SET password_updated=current_timestamp WHERE auth_method='" + XdatUserAuthService.LOCALDB + "' AND password_updated IS NULL"); - complete(); } private static final Logger _log = LoggerFactory.getLogger(UpdateUserAuthTable.class); - @Autowired - @Lazy - private JdbcTemplate _template; - - @Autowired - @Lazy - private XdatUserAuthService _xdatUserAuthService; + private final JdbcTemplate _template; + private final XdatUserAuthService _xdatUserAuthService; } diff --git a/src/main/java/org/nrg/xnat/restlet/XNATApplication.java b/src/main/java/org/nrg/xnat/restlet/XNATApplication.java index f297f018abf18274437323fc2866638744bde574..32913dc4da24963bfca68212f0bcd42e988050aa 100755 --- a/src/main/java/org/nrg/xnat/restlet/XNATApplication.java +++ b/src/main/java/org/nrg/xnat/restlet/XNATApplication.java @@ -46,10 +46,10 @@ public class XNATApplication extends Application { public static final String PREARC_PROJECT_URI = "/prearchive/projects/{PROJECT_ID}"; public static final String PREARC_SESSION_URI = PREARC_PROJECT_URI + "/{SESSION_TIMESTAMP}/{SESSION_LABEL}"; + @SuppressWarnings("WeakerAccess") @JsonIgnore public XNATApplication(Context parentContext) { super(parentContext); - } @Override @@ -364,24 +364,32 @@ public class XNATApplication extends Application { * @param router The URL router for the restlet servlet. * @return A list of classes that should be attached unprotected, i.e. publicly accessible. */ - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "Duplicates"}) private List<Class<? extends Resource>> addExtensionRoutes(Router router) { - Set<String> packages = new HashSet<>(); - final Map<String, XnatRestletExtensions> pkgLists = XDAT.getContextService().getBeansOfType(XnatRestletExtensions.class); - for (XnatRestletExtensions pkgList : pkgLists.values()) { - packages.addAll(pkgList); + final Set<String> packages = new HashSet<>(); + final XnatRestletExtensionsBean extensions = XDAT.getContextService().getBean(XnatRestletExtensionsBean.class); + if (extensions.size() > 0) { + packages.addAll(extensions.getPackages()); + } else { + final XnatRestletExtensions extension = XDAT.getContextService().getBean("defaultXnatRestletExtensions", XnatRestletExtensions.class); + if (extension != null) { + packages.addAll(extension.getPackages()); + } + } + if (packages.size() == 0) { + packages.add("org.nrg.xnat.restlet.extensions"); } - List<Class<? extends Resource>> classes = new ArrayList<>(); - List<Class<? extends Resource>> publicClasses = new ArrayList<>(); + final List<Class<? extends Resource>> classes = new ArrayList<>(); + final List<Class<? extends Resource>> publicClasses = new ArrayList<>(); - for (String pkg : packages) { + for (final String pkg : packages) { try { final List<Class<?>> classesForPackage = Reflection.getClassesForPackage(pkg); if (_log.isDebugEnabled()) { _log.debug("Found " + classesForPackage.size() + " classes for package: " + pkg); } - for (Class<?> clazz : classesForPackage) { + for (final Class<?> clazz : classesForPackage) { if (Resource.class.isAssignableFrom(clazz)) { if (_log.isDebugEnabled()) { _log.debug("Found resource class: " + clazz.getName()); diff --git a/src/main/java/org/nrg/xnat/restlet/XnatRestletExtensions.java b/src/main/java/org/nrg/xnat/restlet/XnatRestletExtensions.java index c583e55faf8870bb26c6f98e3d9bdbf3f041964c..cf51a588d5d7bf159a042fe1b6a1f5633d52e599 100644 --- a/src/main/java/org/nrg/xnat/restlet/XnatRestletExtensions.java +++ b/src/main/java/org/nrg/xnat/restlet/XnatRestletExtensions.java @@ -12,16 +12,23 @@ package org.nrg.xnat.restlet; +import java.util.Collections; import java.util.HashSet; import java.util.Set; -public class XnatRestletExtensions extends HashSet<String> { - public XnatRestletExtensions(Set<String> packages) { - super(); - this.setPackages(packages); +public class XnatRestletExtensions { + public XnatRestletExtensions(final Set<String> packages) { + super(); + _packages.addAll(packages); } - public void setPackages(Set<String> packages) { - clear(); - addAll(packages); + + public int size() { + return _packages.size(); + } + + public Set<String> getPackages() { + return Collections.unmodifiableSet(_packages); } + + private final Set<String> _packages = new HashSet<>(); } diff --git a/src/main/java/org/nrg/xnat/restlet/XnatRestletExtensionsBean.java b/src/main/java/org/nrg/xnat/restlet/XnatRestletExtensionsBean.java new file mode 100644 index 0000000000000000000000000000000000000000..64963b6d53c75333e526a54401cfe5a91ae82387 --- /dev/null +++ b/src/main/java/org/nrg/xnat/restlet/XnatRestletExtensionsBean.java @@ -0,0 +1,39 @@ +/* + * org.nrg.xnat.restlet.XnatRestletExtensionList + * + * Copyright (c) 2016, Washington University School of Medicine + * All Rights Reserved + * + * XNAT is an open-source project of the Neuroinformatics Research Group. + * Released under the Simplified BSD. + * + * Last modified 1/19/16 3:49 PM + */ + +package org.nrg.xnat.restlet; + +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Component +public class XnatRestletExtensionsBean { + public XnatRestletExtensionsBean(final List<XnatRestletExtensions> extensions) { + for (final XnatRestletExtensions extension : extensions) { + _packages.addAll(extension.getPackages()); + } + } + + public int size() { + return _packages.size(); + } + + public Set<String> getPackages() { + return Collections.unmodifiableSet(_packages); + } + + private final Set<String> _packages = new HashSet<>(); +} 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 10b9373fc309310628566f20aa15e53eb2f0e7f1..3d02fb72f854c1b4e99de643fefb8fd953ca5b6f 100644 --- a/src/main/java/org/nrg/xnat/restlet/actions/AutomationBasedImporter.java +++ b/src/main/java/org/nrg/xnat/restlet/actions/AutomationBasedImporter.java @@ -12,44 +12,25 @@ package org.nrg.xnat.restlet.actions; * @author Mike Hodge <hodgem@mir.wustl.edu> */ -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.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.lang.StringEscapeUtils; import org.apache.log4j.Logger; -import org.json.JSONObject; import org.json.JSONArray; import org.json.JSONException; +import org.json.JSONObject; import org.json.JSONTokener; - import org.nrg.action.ClientException; import org.nrg.action.ServerException; import org.nrg.automation.entities.ScriptOutput; import org.nrg.automation.entities.ScriptOutput.Status; +import org.nrg.automation.entities.ScriptTrigger; import org.nrg.automation.event.AutomationEventImplementerI; import org.nrg.automation.event.entities.AutomationCompletionEvent; import org.nrg.automation.event.entities.AutomationEventIdsIds; -import org.nrg.automation.entities.ScriptTrigger; import org.nrg.automation.services.AutomationEventIdsIdsService; import org.nrg.automation.services.AutomationEventIdsService; import org.nrg.automation.services.ScriptTriggerService; @@ -60,15 +41,6 @@ import org.nrg.xdat.XDAT; import org.nrg.xdat.om.XnatExperimentdata; import org.nrg.xdat.om.XnatProjectdata; import org.nrg.xdat.om.XnatSubjectdata; -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.turbine.utils.ArcSpecManager; - -import java.util.zip.ZipOutputStream; - import org.nrg.xft.event.EventMetaI; import org.nrg.xft.event.EventUtils; import org.nrg.xft.event.EventUtils.CATEGORY; @@ -81,6 +53,24 @@ import org.nrg.xft.security.UserI; import org.nrg.xft.utils.zip.TarUtils; import org.nrg.xft.utils.zip.ZipI; import org.nrg.xft.utils.zip.ZipUtils; +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.turbine.utils.ArcSpecManager; + +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.*; +import java.util.concurrent.Callable; +import java.util.zip.ZipOutputStream; @ImporterHandler(handler = "automation", allowCallsWithoutFiles = true, callPartialUriWrap = false) public class AutomationBasedImporter extends ImporterHandlerA implements Callable<List<String>> { @@ -652,8 +642,8 @@ public class AutomationBasedImporter extends ImporterHandlerA implements Callabl returnList.add("ERROR: Could retrieve event service"); return; } - eventService.triggerEvent(automationEvent); - final AutomationCompletionEventListener completionService = AutomationCompletionEventListener.getService(); + eventService.triggerEvent(automationEvent, automationCompletionEvent); + final AutomationCompletionEventListener completionService = XDAT.getContextService().getBeanSafely(AutomationCompletionEventListener.class); List<ScriptOutput> scriptOutputs = null; for (int i = 1; i < TIMEOUT_SECONDS; i++) { try { @@ -795,8 +785,7 @@ public class AutomationBasedImporter extends ImporterHandlerA implements Callabl // 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()); + 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)); diff --git a/src/main/java/org/nrg/xnat/restlet/actions/PrearcBlankSession.java b/src/main/java/org/nrg/xnat/restlet/actions/PrearcBlankSession.java deleted file mode 100644 index 9f5cc72e22aafe3b4fa5263a9fc936da25bdfeb6..0000000000000000000000000000000000000000 --- a/src/main/java/org/nrg/xnat/restlet/actions/PrearcBlankSession.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * org.nrg.xnat.restlet.actions.PrearcBlankSession - * XNAT http://www.xnat.org - * Copyright (c) 2014, Washington University School of Medicine - * All Rights Reserved - * - * Released under the Simplified BSD. - * - * Last modified 7/10/13 9:04 PM - */ -package org.nrg.xnat.restlet.actions; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.apache.log4j.Logger; -import org.nrg.action.ClientException; -import org.nrg.action.ServerException; -import org.nrg.xft.security.UserI; -import org.nrg.xnat.helpers.prearchive.PrearcDatabase; -import org.nrg.xnat.helpers.prearchive.PrearcUtils; -import org.nrg.xnat.helpers.prearchive.PrearcUtils.PrearcStatus; -import org.nrg.xnat.helpers.prearchive.SessionData; -import org.nrg.xnat.restlet.actions.importer.ImporterHandlerA; -import org.nrg.xnat.restlet.util.FileWriterWrapperI; -import org.restlet.data.Status; - -public class PrearcBlankSession extends ImporterHandlerA { - static Logger logger = Logger.getLogger(PrearcBlankSession.class); - - private static final String PROJECT = "project"; - private static final String SUID= "suid"; - private static final String SESSION = "session"; - private static final String SUBJECT = "subject"; // optional, added to the prearchive table if it is present - - private final UserI user; - private final FileWriterWrapperI fi; // never used, we only check if it is null - private final Map<String,Object> params; - - /** - * Check that \"project\", \"session\" and \"suid\" are present. - * - * @param params - * @param fi - * @throws ClientException - */ - private void validate(Map<String,Object> params, FileWriterWrapperI fi) throws ClientException { - if (!params.containsKey(PROJECT) || - !params.containsKey(SUID) || - !params.containsKey(SESSION)) { - throw new ClientException(Status.CLIENT_ERROR_BAD_REQUEST,"Cannot build a blank row for this session without the \"project\", \"session\" and \"suid\" parameters", new IllegalArgumentException()); - } - if (fi != null) { - throw new ClientException(Status.CLIENT_ERROR_BAD_REQUEST, "Cannot upload binary data while creating a blank row for this session", new IllegalArgumentException()); - } - } - - /** - * Helper class to create a blank row in the prearchive table - * @param uID2 - * @param u - * @param fi - * @param project_id - * @param additionalValues - */ - public PrearcBlankSession(final Object uID2 , // ignored - final UserI u, // ignored - final FileWriterWrapperI fi, // should be null, we are not expecting a file when creating a blank row - Map<String,Object> params - ){ - super((uID2==null)?u:uID2,u,fi,params); - this.user=u; - this.fi = fi; - this.params = params; - } - - @Override - public List<String> call() throws ClientException,ServerException{ - this.validate(this.params, this.fi); - String project = (String) this.params.get(PROJECT); - String session = (String) this.params.get(SESSION); - String suid = (String) this.params.get(SUID); - - try { - SessionData blankSession = PrearcUtils.blankSession(project, session, suid); - Collection<SessionData> dupes = PrearcDatabase.getSessionByUID(suid); - if (dupes.size() != 0) { - throw new ClientException(Status.CLIENT_ERROR_BAD_REQUEST, "A session with Study Instance UID " + suid + " exists.", new IllegalArgumentException()); - } - blankSession.setStatus(PrearcStatus.RECEIVING); - if (this.params.containsKey(SUBJECT)) { - blankSession.setSubject((String)this.params.get(SUBJECT)); - } - PrearcDatabase.addSession(blankSession); - Map<String,Object> additionalValues = new HashMap<String,Object>(); - additionalValues.put(SUID, blankSession.getTag()); - List<String> ret = new ArrayList<String>(); - ret.add(PrearcUtils.buildURI(blankSession.getProject(), blankSession.getTimestamp(), blankSession.getFolderName())); - return ret; - } - catch (Exception e) { - logger.error("Unable to create blank session", e); - throw new ClientException(Status.SERVER_ERROR_INTERNAL,e.getMessage(), new IllegalArgumentException()); - } - } -} diff --git a/src/main/java/org/nrg/xnat/restlet/extensions/DicomSCPRestlet.java b/src/main/java/org/nrg/xnat/restlet/extensions/DicomSCPRestlet.java index 330db9e537fe7905119834990dea955dae16134f..cc2dd960a390e926f794525b6ea7ff6d059ad6e3 100644 --- a/src/main/java/org/nrg/xnat/restlet/extensions/DicomSCPRestlet.java +++ b/src/main/java/org/nrg/xnat/restlet/extensions/DicomSCPRestlet.java @@ -14,6 +14,7 @@ package org.nrg.xnat.restlet.extensions; import com.google.common.base.Joiner; import org.apache.commons.lang3.StringUtils; import org.nrg.dcm.DicomSCPManager; +import org.nrg.dcm.exceptions.EnabledDICOMReceiverWithDuplicatePortException; import org.nrg.dcm.preferences.DicomSCPInstance; import org.nrg.framework.exceptions.NrgServiceError; import org.nrg.framework.exceptions.NrgServiceException; @@ -150,7 +151,11 @@ public class DicomSCPRestlet extends SecureResource { if (_scpId == null) { getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "You must specify a specific DICOM SCP instance to enable."); } else { - _dicomSCPManager.enableDicomSCP(_scpId); + try { + _dicomSCPManager.enableDicomSCP(_scpId); + } catch (EnabledDICOMReceiverWithDuplicatePortException e) { + getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "There is already another DICOM SCP instance enabled with the same port: " + e.getExisting().toString()); + } returnDefaultRepresentation(); } } else if (_action.equalsIgnoreCase("disable")) { diff --git a/src/main/java/org/nrg/xnat/restlet/resources/ProjectResource.java b/src/main/java/org/nrg/xnat/restlet/resources/ProjectResource.java index 21901c1b443ab3e25a00027045d2fe4fb5dbf51e..bbbd370c4f5f30d9cbe6da090c3cc2bd7cccdcc2 100644 --- a/src/main/java/org/nrg/xnat/restlet/resources/ProjectResource.java +++ b/src/main/java/org/nrg/xnat/restlet/resources/ProjectResource.java @@ -132,12 +132,6 @@ public class ProjectResource extends ItemResource { if (project == null || Permissions.canEdit(user, project)) { XFTItem item = loadItem("xnat:projectData", true); - //Hacky fix. Something changed so that loadItem no longer produces null when handlePut expects it to. - // This fix nulls the item to allow the project item to be stored in the item object. - if(item!=null && item.toString().equals("<XFTItem name=\"xnat:projectData\"/>\r\n")){ - item=null; - } - if (item == null) { String xsiType = getQueryVariable("xsiType"); if (xsiType != null) { @@ -166,8 +160,8 @@ public class ProjectResource extends ItemResource { if (filepath != null && !filepath.equals("")) { if (project.getId() == null) { - item = project.getItem(); - this.project = project; + item = this.project.getItem(); + project = this.project; } if (!Permissions.canEdit(user, item)) { diff --git a/src/main/java/org/nrg/xnat/restlet/resources/prearchive/PrearcSessionResource.java b/src/main/java/org/nrg/xnat/restlet/resources/prearchive/PrearcSessionResource.java index 28761e00d3004b6be0c9ebb062309262751bed0c..98bcad9be393db5291be735669c5847bb1cabaf7 100644 --- a/src/main/java/org/nrg/xnat/restlet/resources/prearchive/PrearcSessionResource.java +++ b/src/main/java/org/nrg/xnat/restlet/resources/prearchive/PrearcSessionResource.java @@ -404,7 +404,7 @@ public final class PrearcSessionResource extends SecureResource { return new FileRepresentation(sessionXML, variant.getMediaType(), 0); } else if (MediaType.APPLICATION_JSON.equals(mt)) { List<SessionDataTriple> l = new ArrayList<SessionDataTriple>(); - l.add(new SessionDataTriple().setFolderName(sessionDir.getName()).setProject(project).setTimestamp(timestamp)); + l.add(new SessionDataTriple(sessionDir.getName(), project, timestamp)); XFTTable table = null; try { table = PrearcUtils.convertArrayLtoTable(PrearcDatabase.buildRows(l)); diff --git a/src/main/java/org/nrg/xnat/restlet/services/Importer.java b/src/main/java/org/nrg/xnat/restlet/services/Importer.java index e73c3a720f082576e74c4ac9ac5c3bcae0c1e1a3..db5d926bcc3586ad1413cc76b5f6f926e389edff 100644 --- a/src/main/java/org/nrg/xnat/restlet/services/Importer.java +++ b/src/main/java/org/nrg/xnat/restlet/services/Importer.java @@ -301,9 +301,28 @@ public class Importer extends SecureResource { } protected void respondToException(Exception e, Status status) { - logger.error("",e); + final Throwable cause = e.getCause(); + if (cause != null && cause instanceof ExceptionInInitializerError && ((ExceptionInInitializerError) cause).getException() != null) { + final ExceptionInInitializerError error = (ExceptionInInitializerError) cause; + final StringBuilder buffer = new StringBuilder("An error occurred initializing an object during the import operation: "); + buffer.append(error.getException().getMessage()); + final StackTraceElement[] stackTrace = error.getException().getStackTrace(); + if (stackTrace != null) { + int lines = 0; + for (final StackTraceElement element : stackTrace) { + buffer.append(System.lineSeparator()).append(" ").append(element.toString()); + lines++; + if (lines > 5) { + break; + } + } + } + logger.error(buffer.toString()); + } else { + logger.error("", e); + } if (this.requested_format!=null && this.requested_format.equalsIgnoreCase("HTML")) { - response = new ArrayList<String>(); + response = new ArrayList<>(); response.add(e.getMessage()); returnDefaultRepresentation(); } else { diff --git a/src/main/java/org/nrg/xnat/restlet/servlet/XNATRestletServlet.java b/src/main/java/org/nrg/xnat/restlet/servlet/XNATRestletServlet.java index 743892dc91173fb0e8e7aee4ccf61d594bad420a..fae852c24a6b9ba061152233ae80d22277197f0e 100644 --- a/src/main/java/org/nrg/xnat/restlet/servlet/XNATRestletServlet.java +++ b/src/main/java/org/nrg/xnat/restlet/servlet/XNATRestletServlet.java @@ -24,7 +24,7 @@ import javax.servlet.ServletException; public class XNATRestletServlet extends ServerServlet { private static final long serialVersionUID = -4149339105144231596L; - public static ServletConfig REST_CONFIG=null; + public static ServletConfig REST_CONFIG = null; private final Logger logger = LoggerFactory.getLogger(XNATRestletServlet.class); @@ -32,13 +32,17 @@ public class XNATRestletServlet extends ServerServlet { public void init() throws ServletException { super.init(); - XNATRestletServlet.REST_CONFIG=this.getServletConfig(); - - PrearcConfig prearcConfig = XDAT.getContextService().getBean(PrearcConfig.class); - try { - PrearcDatabase.initDatabase(prearcConfig.isReloadPrearcDatabaseOnApplicationStartup()); - } catch (Throwable e) { - logger.error("Unable to initialize prearchive database", e); + XNATRestletServlet.REST_CONFIG = getServletConfig(); + + final PrearcConfig prearcConfig = XDAT.getContextService().getBean(PrearcConfig.class); + if (prearcConfig != null) { + try { + PrearcDatabase.initDatabase(prearcConfig.isReloadPrearcDatabaseOnApplicationStartup()); + } catch (Throwable e) { + logger.error("Unable to initialize prearchive database", e); + } + } else { + logger.error("The prearc config wasn't found!"); } XDAT.getContextService().getBean(DicomSCPManager.class).startOrStopDicomSCPAsDictatedByConfiguration(); diff --git a/src/main/java/org/nrg/xnat/security/FilterSecurityInterceptorBeanPostProcessor.java b/src/main/java/org/nrg/xnat/security/FilterSecurityInterceptorBeanPostProcessor.java index 78b409aa70018a31fbd1c5bb45646f710d607ca5..2144dae6ecb6583b0a74eea5418cb7b39bb89127 100644 --- a/src/main/java/org/nrg/xnat/security/FilterSecurityInterceptorBeanPostProcessor.java +++ b/src/main/java/org/nrg/xnat/security/FilterSecurityInterceptorBeanPostProcessor.java @@ -12,8 +12,9 @@ package org.nrg.xnat.security; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.nrg.xdat.preferences.InitializerSiteConfiguration; +import org.nrg.xdat.preferences.SiteConfigPreferences; import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; @@ -24,13 +25,17 @@ import org.springframework.security.web.access.intercept.FilterSecurityIntercept import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; -import javax.inject.Inject; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; public class FilterSecurityInterceptorBeanPostProcessor implements BeanPostProcessor { + @Autowired + public FilterSecurityInterceptorBeanPostProcessor(final SiteConfigPreferences preferences) { + _preferences = preferences; + } + public void setOpenUrls(List<String> openUrls) { _openUrls.clear(); _openUrls.addAll(openUrls); @@ -48,8 +53,8 @@ public class FilterSecurityInterceptorBeanPostProcessor implements BeanPostProce } if (bean instanceof FilterSecurityInterceptor) { - FilterSecurityInterceptor interceptor = (FilterSecurityInterceptor) bean; - final ExpressionBasedFilterInvocationSecurityMetadataSource metadataSource = getMetadataSource(_service.getRequireLogin()); + final FilterSecurityInterceptor interceptor = (FilterSecurityInterceptor) bean; + final ExpressionBasedFilterInvocationSecurityMetadataSource metadataSource = getMetadataSource(_preferences.getRequireLogin()); if (_log.isDebugEnabled()) { _log.debug("Found a FilterSecurityInterceptor bean with the following metadata configuration:"); displayMetadataSource(interceptor.getSecurityMetadataSource()); @@ -109,8 +114,7 @@ public class FilterSecurityInterceptorBeanPostProcessor implements BeanPostProce private static final String ADMIN_EXPRESSION = "hasRole('ROLE_ADMIN')"; private static final String DEFAULT_EXPRESSION = "hasRole('ROLE_USER')"; - @Inject - private InitializerSiteConfiguration _service; + private final SiteConfigPreferences _preferences; private final List<String> _openUrls = new ArrayList<>(); private final List<String> _adminUrls = new ArrayList<>(); diff --git a/src/main/java/org/nrg/xnat/security/XnatAuthenticationEntryPoint.java b/src/main/java/org/nrg/xnat/security/XnatAuthenticationEntryPoint.java index 1c40b2b9f1aa27bff5000df7a4b61f55e6943f8b..ee237117ae2b0bcae2a0fa77a565637ba74b2973 100644 --- a/src/main/java/org/nrg/xnat/security/XnatAuthenticationEntryPoint.java +++ b/src/main/java/org/nrg/xnat/security/XnatAuthenticationEntryPoint.java @@ -13,8 +13,7 @@ package org.nrg.xnat.security; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.nrg.config.exceptions.SiteConfigurationException; -import org.nrg.xdat.preferences.InitializerSiteConfiguration; +import org.nrg.xdat.preferences.SiteConfigPreferences; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @@ -30,9 +29,9 @@ import java.util.regex.Pattern; public class XnatAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint { - public XnatAuthenticationEntryPoint(final String loginFormUrl, final InitializerSiteConfiguration configuration) { + public XnatAuthenticationEntryPoint(final String loginFormUrl, final SiteConfigPreferences preferences) { super(loginFormUrl); - _configuration = configuration; + _preferences = preferences; } /** @@ -70,14 +69,8 @@ public class XnatAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoi } if (isDataPath(request) && !isInteractiveAgent(userAgent)) { - try { - response.setHeader("WWW-Authenticate", "Basic realm=\"" + _configuration.getSiteId() + "\""); - response.sendError(HttpServletResponse.SC_UNAUTHORIZED); - } catch (SiteConfigurationException e) { - _log.error("An error occurred trying to access system resources: siteId", e); - response.setHeader("WWW-Authenticate", "Basic"); - response.sendError(HttpServletResponse.SC_UNAUTHORIZED); - } + response.setHeader("WWW-Authenticate", "Basic realm=\"" + _preferences.getSiteId() + "\""); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED); } else { super.commence(request, response, authException); } @@ -156,7 +149,7 @@ public class XnatAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoi return false; } - private final InitializerSiteConfiguration _configuration; + private final SiteConfigPreferences _preferences; private static final Log _log = LogFactory.getLog(XnatAuthenticationEntryPoint.class); diff --git a/src/main/java/org/nrg/xnat/security/XnatAuthenticationFilter.java b/src/main/java/org/nrg/xnat/security/XnatAuthenticationFilter.java index dad3fd4f943d0b9ccfcc879b888f5c84b250a0e6..ae2d1c99ffc0ba2aeaac25f3af944a854e43e718 100644 --- a/src/main/java/org/nrg/xnat/security/XnatAuthenticationFilter.java +++ b/src/main/java/org/nrg/xnat/security/XnatAuthenticationFilter.java @@ -14,26 +14,62 @@ import com.google.common.collect.Maps; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.nrg.xdat.security.XDATUser; 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.turbine.utils.AccessLogger; import org.nrg.xft.XFTItem; import org.nrg.xft.event.EventMetaI; +import org.nrg.xft.event.EventUtils; import org.nrg.xft.utils.SaveItemHelper; +import org.nrg.xnat.turbine.utils.ProjectAccessRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.crypto.codec.Base64; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; -import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.UnsupportedEncodingException; import java.util.Calendar; import java.util.Map; -public class XnatAuthenticationFilter extends UsernamePasswordAuthenticationFilter{ +public class XnatAuthenticationFilter extends UsernamePasswordAuthenticationFilter { + @Autowired + public void setAuthenticationManager(final AuthenticationManager authenticationManager) { + super.setAuthenticationManager(authenticationManager); + } + + @Autowired + public void setXnatProviderManager(final XnatProviderManager providerManager) { + _providerManager = providerManager; + } + + @Autowired + @Override + public void setAuthenticationSuccessHandler(final AuthenticationSuccessHandler handler) { + super.setAuthenticationSuccessHandler(handler); + } + + @Autowired + @Override + public void setAuthenticationFailureHandler(final AuthenticationFailureHandler handler) { + super.setAuthenticationFailureHandler(handler); + } + + @Autowired + @Override + public void setSessionAuthenticationStrategy(final SessionAuthenticationStrategy strategy) { + super.setSessionAuthenticationStrategy(strategy); + } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { @@ -43,17 +79,16 @@ public class XnatAuthenticationFilter extends UsernamePasswordAuthenticationFilt // If we didn't find a username if (StringUtils.isBlank(username)) { // See if there's an authorization header. - String header = request.getHeader("Authorization"); + final String header = request.getHeader("Authorization"); if (!StringUtils.isBlank(header) && header.startsWith("Basic ")) { - byte[] base64Token; try { - base64Token = header.substring(6).getBytes("UTF-8"); - String token = new String(Base64.decode(base64Token), "UTF-8"); - int delim = token.indexOf(":"); + final byte[] base64Token = header.substring(6).getBytes("UTF-8"); + final String token = new String(Base64.decode(base64Token), "UTF-8"); + final int position = token.indexOf(":"); - if (delim != -1) { - username = token.substring(0, delim); - password = token.substring(delim + 1); + if (position != -1) { + username = token.substring(0, position); + password = token.substring(position + 1); } if (_log.isDebugEnabled()) { _log.debug("Basic Authentication Authorization header found for user '" + username + "'"); @@ -66,73 +101,91 @@ public class XnatAuthenticationFilter extends UsernamePasswordAuthenticationFilt //SHOULD we be throwing an exception if the username is null? - String providerName=request.getParameter("login_method"); - UsernamePasswordAuthenticationToken authRequest; + final String providerName = request.getParameter("login_method"); + final UsernamePasswordAuthenticationToken authRequest; - if(StringUtils.isEmpty(providerName) && !StringUtils.isEmpty(username)){ - //try to guess the auth_method - String auth_method = _providerManager.retrieveAuthMethod(username); - if(StringUtils.isEmpty(auth_method)){ + if (StringUtils.isEmpty(providerName) && !StringUtils.isEmpty(username)) { + // Try to guess the auth method + final String authMethod = _providerManager.retrieveAuthMethod(username); + if (StringUtils.isEmpty(authMethod)) { throw new BadCredentialsException("Missing login_method parameter."); + } else { + authRequest = _providerManager.buildUPTokenForAuthMethod(authMethod, username, password); } - else { - authRequest=_providerManager.buildUPTokenForAuthMethod(auth_method,username,password); - } - } - else { - authRequest=_providerManager.buildUPTokenForProviderName(providerName,username,password); + } else { + authRequest = _providerManager.buildUPTokenForProviderName(providerName, username, password); } setDetails(request, authRequest); try { - Authentication auth= super.getAuthenticationManager().authenticate(authRequest); - AccessLogger.LogServiceAccess(username, AccessLogger.GetRequestIp(request), "Authentication", "SUCCESS"); - return auth; - } catch (AuthenticationException e) { - logFailedAttempt(username, request); - throw e; - } + AccessLogger.LogServiceAccess(username, AccessLogger.GetRequestIp(request), "Authentication", "SUCCESS"); + Authentication auth = getAuthenticationManager().authenticate(authRequest); + + //Fixed XNAT-4409 by adding a check for a par parameter on login. If a PAR is present and valid, then grant the user that just logged in the appropriate project permissions. + if(StringUtils.isNotBlank(request.getParameter("par"))){ + String parId = request.getParameter("par"); + request.getSession().setAttribute("par", parId); + ProjectAccessRequest par = ProjectAccessRequest.RequestPARByGUID(parId, null); + if (par.getApproved() != null || par.getApprovalDate() != null) { + logger.debug("PAR not approved or already accepted: " + par.getGuid()); + } else { + XDATUser user = new XDATUser(username); + par.process(user, true, EventUtils.TYPE.WEB_FORM, "", ""); + } + } + + return auth; + } catch (AuthenticationException e) { + logFailedAttempt(username, request); + throw e; + } catch (UserNotFoundException e) { + _log.error("",e); + } catch (UserInitException e) { + _log.error("",e); + } catch (Exception e) { + _log.error("",e); + } + return null; } - public static void logFailedAttempt(String username, HttpServletRequest req){ - if (!StringUtils.isBlank(username)) { - Integer uid=retrieveUserId(username); - if(uid!=null){ - try { - XFTItem item = XFTItem.NewItem("xdat:user_login",null); - item.setProperty("xdat:user_login.user_xdat_user_id",uid); - item.setProperty("xdat:user_login.ip_address",AccessLogger.GetRequestIp(req)); + public static void logFailedAttempt(String username, HttpServletRequest req) { + if (!StringUtils.isBlank(username)) { + final Integer uid = retrieveUserId(username); + if (uid != null) { + try { + XFTItem item = XFTItem.NewItem("xdat:user_login", null); + item.setProperty("xdat:user_login.user_xdat_user_id", uid); + item.setProperty("xdat:user_login.ip_address", AccessLogger.GetRequestIp(req)); item.setProperty("xdat:user_login.login_date", Calendar.getInstance(java.util.TimeZone.getDefault()).getTime()); - SaveItemHelper.authorizedSave(item,null,true,false,(EventMetaI)null); + SaveItemHelper.authorizedSave(item, null, true, false, (EventMetaI) null); } catch (Exception exception) { _log.error(exception); - } - } - AccessLogger.LogServiceAccess(username, AccessLogger.GetRequestIp(req), "Authentication", "FAILED"); - } + } + } + AccessLogger.LogServiceAccess(username, AccessLogger.GetRequestIp(req), "Authentication", "FAILED"); + } } - - public static Integer retrieveUserId(String username){ - synchronized(checked){ - if(username==null){ - return null; - } - - if(checked.containsKey(username)){ - return checked.get(username); - } - - Integer i=Users.getUserid(username); - checked.put(username, i); - - return i; - } + + public static Integer retrieveUserId(String username) { + synchronized (checked) { + if (username == null) { + return null; + } + + if (checked.containsKey(username)) { + return checked.get(username); + } + + final Integer i = Users.getUserid(username); + checked.put(username, i); + + return i; + } } - - private static final Log _log = LogFactory.getLog(XnatAuthenticationFilter.class); + + private static final Log _log = LogFactory.getLog(XnatAuthenticationFilter.class); private static final Map<String, Integer> checked = Maps.newHashMap(); - @Inject private XnatProviderManager _providerManager; } diff --git a/src/main/java/org/nrg/xnat/security/XnatBasicAuthenticationFilter.java b/src/main/java/org/nrg/xnat/security/XnatBasicAuthenticationFilter.java index 21d71b33f900ad461a2895a80fe913647924d661..49340a9e7471ef311fadb178ebe4dcec96648f4c 100644 --- a/src/main/java/org/nrg/xnat/security/XnatBasicAuthenticationFilter.java +++ b/src/main/java/org/nrg/xnat/security/XnatBasicAuthenticationFilter.java @@ -19,8 +19,8 @@ import org.nrg.xft.XFTItem; import org.nrg.xft.event.EventUtils; import org.nrg.xft.security.UserI; import org.nrg.xft.utils.SaveItemHelper; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AnonymousAuthenticationToken; -import org.springframework.security.authentication.AuthenticationDetailsSource; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; @@ -29,11 +29,9 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.codec.Base64; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; -import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy; import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; -import javax.inject.Inject; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -44,19 +42,24 @@ import java.util.Date; import java.util.Map; public class XnatBasicAuthenticationFilter extends BasicAuthenticationFilter { + @Autowired + public XnatBasicAuthenticationFilter(final AuthenticationManager manager, final AuthenticationEntryPoint entryPoint) { + super(manager, entryPoint); + _authenticationDetailsSource = new WebAuthenticationDetailsSource(); + } - @Inject - private XnatProviderManager _providerManager; - - private AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource(); - private SessionAuthenticationStrategy sessionStrategy = new NullAuthenticatedSessionStrategy(); + @Autowired + public void setXnatProviderManager(final XnatProviderManager providerManager) { + _providerManager = providerManager; + } - public XnatBasicAuthenticationFilter(AuthenticationManager manager, AuthenticationEntryPoint entryPoint) { - super(manager, entryPoint); + @Autowired + public void setSessionAuthenticationStrategy(final SessionAuthenticationStrategy strategy) { + _authenticationStrategy = strategy; } private boolean authenticationIsRequired(String username) { - // Only reauthenticate if username doesn't match SecurityContextHolder and user isn't authenticated + // Only re-authenticate if username doesn't match SecurityContextHolder and user isn't authenticated // (see SEC-53) Authentication existingAuth = SecurityContextHolder.getContext().getAuthentication(); @@ -74,7 +77,7 @@ public class XnatBasicAuthenticationFilter extends BasicAuthenticationFilter { // Handle unusual condition where an AnonymousAuthenticationToken is already present // This shouldn't happen very often, as BasicProcessingFilter is meant to be earlier in the filter // chain than AnonymousAuthenticationFilter. Nevertheless, presence of both an AnonymousAuthenticationToken - // together with a BASIC authentication request header should indicate reauthentication using the + // together with a BASIC authentication request header should indicate re-authentication using the // BASIC protocol is desirable. This behaviour is also consistent with that provided by form and digest, // both of which force re-authentication if the respective header is detected (and in doing so replace // any existing AnonymousAuthenticationToken). See SEC-610. @@ -89,15 +92,15 @@ public class XnatBasicAuthenticationFilter extends BasicAuthenticationFilter { if ((header != null) && header.startsWith("Basic ")) { byte[] base64Token = header.substring(6).getBytes("UTF-8"); - String token = new String(Base64.decode(base64Token), getCredentialsCharset(request)); + String token = new String(Base64.decode(base64Token), getCredentialsCharset(request)); String username = ""; String password = ""; - int delim = token.indexOf(":"); + int position = token.indexOf(":"); - if (delim != -1) { - username = token.substring(0, delim); - password = token.substring(delim + 1); + if (position != -1) { + username = token.substring(0, position); + password = token.substring(position + 1); } if (debug) { @@ -105,16 +108,15 @@ public class XnatBasicAuthenticationFilter extends BasicAuthenticationFilter { } if (authenticationIsRequired(username)) { - UsernamePasswordAuthenticationToken authRequest = - _providerManager.buildUPTokenForAuthMethod(_providerManager.retrieveAuthMethod(username), username, password); - authRequest.setDetails(authenticationDetailsSource.buildDetails(request)); + UsernamePasswordAuthenticationToken authRequest = _providerManager.buildUPTokenForAuthMethod(_providerManager.retrieveAuthMethod(username), username, password); + authRequest.setDetails(_authenticationDetailsSource.buildDetails(request)); Authentication authResult; try { authResult = getAuthenticationManager().authenticate(authRequest); - sessionStrategy.onAuthentication(authResult, request, response); + _authenticationStrategy.onAuthentication(authResult, request, response); } catch (AuthenticationException failed) { @@ -160,9 +162,10 @@ public class XnatBasicAuthenticationFilter extends BasicAuthenticationFilter { lock = locks.get(user.getID()); } + //noinspection SynchronizationOnLocalVariableOrMethodParameter synchronized (lock) { - Date today = Calendar.getInstance(java.util.TimeZone.getDefault()).getTime(); - XFTItem item = XFTItem.NewItem("xdat:user_login", user); + Date today = Calendar.getInstance(java.util.TimeZone.getDefault()).getTime(); + XFTItem item = XFTItem.NewItem("xdat:user_login", user); item.setProperty("xdat:user_login.user_xdat_user_id", user.getID()); item.setProperty("xdat:user_login.login_date", today); item.setProperty("xdat:user_login.ip_address", AccessLogger.GetRequestIp(request)); @@ -178,14 +181,7 @@ public class XnatBasicAuthenticationFilter extends BasicAuthenticationFilter { super.onSuccessfulAuthentication(request, response, authResult); } - /** - * The session handling strategy which will be invoked immediately after an authentication request is successfully - * processed by the <tt>AuthenticationManager</tt>. Used, for example, to handle changing of the session identifier - * to prevent session fixation attacks. - * - * @param sessionStrategy the implementation to use. If not set a null implementation is used. - */ - public void setSessionAuthenticationStrategy(SessionAuthenticationStrategy sessionStrategy) { - this.sessionStrategy = sessionStrategy; - } + private final WebAuthenticationDetailsSource _authenticationDetailsSource; + private XnatProviderManager _providerManager; + private SessionAuthenticationStrategy _authenticationStrategy; } diff --git a/src/main/java/org/nrg/xnat/security/XnatExpiredPasswordFilter.java b/src/main/java/org/nrg/xnat/security/XnatExpiredPasswordFilter.java index e6be0dbdef9544ab1f999892726e515a0ace8227..3db8387c66b81d8981b5a66a267c6f707b1e93d6 100644 --- a/src/main/java/org/nrg/xnat/security/XnatExpiredPasswordFilter.java +++ b/src/main/java/org/nrg/xnat/security/XnatExpiredPasswordFilter.java @@ -12,12 +12,12 @@ package org.nrg.xnat.security; import org.apache.commons.lang3.StringUtils; import org.nrg.config.exceptions.SiteConfigurationException; -import org.nrg.framework.services.ContextService; +import org.nrg.framework.exceptions.NrgServiceError; import org.nrg.xdat.XDAT; import org.nrg.xdat.entities.AliasToken; import org.nrg.xdat.entities.UserRole; import org.nrg.xdat.om.ArcArchivespecification; -import org.nrg.xdat.preferences.InitializerSiteConfiguration; +import org.nrg.xdat.preferences.SiteConfigPreferences; import org.nrg.xdat.security.helpers.Roles; import org.nrg.xdat.services.AliasTokenService; import org.nrg.xdat.services.XdatUserAuthService; @@ -25,58 +25,50 @@ import org.nrg.xdat.turbine.utils.TurbineUtils; import org.nrg.xft.security.UserI; import org.nrg.xnat.turbine.utils.ArcSpecManager; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.security.crypto.codec.Base64; import org.springframework.web.filter.GenericFilterBean; -import javax.inject.Inject; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import javax.sql.DataSource; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.sql.ResultSet; import java.sql.SQLException; +import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; @SuppressWarnings({"SqlDialectInspection", "SqlNoDataSourceInspection"}) public class XnatExpiredPasswordFilter extends GenericFilterBean { - private String changePasswordPath = ""; - private String changePasswordDestination = ""; - private String logoutDestination = ""; - private String loginPath = ""; - private String loginDestination = ""; - private String inactiveAccountPath; - private String inactiveAccountDestination; - private String emailVerificationDestination; - private String emailVerificationPath; - private boolean passwordExpirationDirtied = true; - private boolean passwordExpirationDisabled; - private boolean passwordExpirationInterval; - private String passwordExpirationSetting; + @Autowired + public XnatExpiredPasswordFilter(final SiteConfigPreferences preferences, final JdbcTemplate jdbcTemplate, final AliasTokenService aliasTokenService) { + super(); + _preferences = preferences; + _aliasTokenService = aliasTokenService; + _jdbcTemplate = jdbcTemplate; + } @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { - final HttpServletRequest request = (HttpServletRequest) req; - final HttpServletResponse response = (HttpServletResponse) res; - UserI user = XDAT.getUserDetails(); - Object passwordExpired = request.getSession().getAttribute("expired"); + final HttpServletRequest request = (HttpServletRequest) req; + final HttpServletResponse response = (HttpServletResponse) res; + UserI user = XDAT.getUserDetails(); + Object passwordExpired = request.getSession().getAttribute("expired"); // MIGRATION: Need to remove arcspec. ArcArchivespecification _arcSpec = ArcSpecManager.GetInstance(); - final String referer = request.getHeader("Referer"); + final String referer = request.getHeader("Referer"); if (request.getSession() != null && request.getSession().getAttribute("forcePasswordChange") != null && (Boolean) request.getSession().getAttribute("forcePasswordChange")) { try { String refererPath = null; - String uri = new URI(request.getRequestURI()).getPath(); + String uri = new URI(request.getRequestURI()).getPath(); if (!StringUtils.isBlank(referer)) { refererPath = new URI(referer).getPath(); } @@ -106,14 +98,14 @@ public class XnatExpiredPasswordFilter extends GenericFilterBean { if (header != null && header.startsWith("Basic ")) { //For users that authenticated using basic authentication, check whether their password is expired, and if so give them a 403 and a message that they need to change their password. - String token = new String(Base64.decode(header.substring(6).getBytes("UTF-8")), "UTF-8"); + String token = new String(Base64.decode(header.substring(6).getBytes("UTF-8")), "UTF-8"); String username = ""; - int delim = token.indexOf(":"); + int delim = token.indexOf(":"); if (delim != -1) { username = token.substring(0, delim); } if (AliasToken.isAliasFormat(username)) { - AliasToken alias = getAliasTokenService().locateToken(username); + AliasToken alias = _aliasTokenService.locateToken(username); if (alias == null) { response.sendError(HttpServletResponse.SC_FORBIDDEN, "Your security token has expired. Please try again after updating your session."); return; @@ -122,7 +114,7 @@ public class XnatExpiredPasswordFilter extends GenericFilterBean { } // Check whether the user is connected to an active role for non_expiring. try { - List<Integer> roles = (new JdbcTemplate(_dataSource)).query("SELECT COUNT(*) FROM xhbm_user_role where username = ? and role = ? and enabled = 't'", new String[] {username, UserRole.ROLE_NON_EXPIRING}, new RowMapper<Integer>() { + List<Integer> roles = _jdbcTemplate.query("SELECT COUNT(*) FROM xhbm_user_role where username = ? and role = ? and enabled = 't'", new String[]{username, UserRole.ROLE_NON_EXPIRING}, new RowMapper<Integer>() { public Integer mapRow(ResultSet rs, int rowNum) throws SQLException { return rs.getInt(1); } @@ -183,7 +175,7 @@ public class XnatExpiredPasswordFilter extends GenericFilterBean { chain.doFilter(req, res); } else if (user.isEnabled()) { boolean isExpired = checkForExpiredPassword(user); - boolean requireSalted = useSiteConfigPrefs? XDAT.getSiteConfigPreferences().getRequireSaltedPasswords() : _initializerPreferences.getRequireSaltedPasswords(); + boolean requireSalted = useSiteConfigPrefs ? _preferences.getRequireSaltedPasswords() : _preferences.getRequireSaltedPasswords(); if ((!isUserNonExpiring(user) && isExpired) || (requireSalted && user.getSalt() == null)) { request.getSession().setAttribute("expired", isExpired); response.sendRedirect(TurbineUtils.GetFullServerPath() + changePasswordPath); @@ -254,6 +246,11 @@ public class XnatExpiredPasswordFilter extends GenericFilterBean { this.passwordExpirationDirtied = passwordExpirationDirtied; } + public void refreshFromSiteConfig() { + useSiteConfigPrefs = true; + passwordExpirationDirtied = true; + } + private boolean checkForExpiredPassword(final UserI user) { return checkForExpiredPassword(user.getUsername()); } @@ -264,14 +261,14 @@ public class XnatExpiredPasswordFilter extends GenericFilterBean { return false; } if (isPasswordExpirationInterval()) { - List<Boolean> expired = (new JdbcTemplate(_dataSource)).query("SELECT ((now()-password_updated)> (Interval '" + passwordExpirationSetting + "')) AS expired FROM xhbm_xdat_user_auth WHERE auth_user = ? AND auth_method = 'localdb'", new String[] {username}, new RowMapper<Boolean>() { + List<Boolean> expired = _jdbcTemplate.query("SELECT ((now()-password_updated)> (Interval '" + passwordExpirationSetting + "')) AS expired FROM xhbm_xdat_user_auth WHERE auth_user = ? AND auth_method = 'localdb'", new String[]{username}, new RowMapper<Boolean>() { public Boolean mapRow(ResultSet rs, int rowNum) throws SQLException { return rs.getBoolean(1); } }); return expired.get(0); } else { - List<Boolean> expired = (new JdbcTemplate(_dataSource)).query("SELECT (to_date('" + new SimpleDateFormat("MM/dd/yyyy").format(new Date(Long.parseLong(passwordExpirationSetting))) + "', 'MM/DD/YYYY') BETWEEN password_updated AND now()) AS expired FROM xhbm_xdat_user_auth WHERE auth_user = ? AND auth_method = 'localdb'", new String[] {username}, new RowMapper<Boolean>() { + List<Boolean> expired = _jdbcTemplate.query("SELECT (to_date('" + new SimpleDateFormat("MM/dd/yyyy").format(new Date(Long.parseLong(passwordExpirationSetting))) + "', 'MM/DD/YYYY') BETWEEN password_updated AND now()) AS expired FROM xhbm_xdat_user_auth WHERE auth_user = ? AND auth_method = 'localdb'", new String[]{username}, new RowMapper<Boolean>() { public Boolean mapRow(ResultSet rs, int rowNum) throws SQLException { return rs.getBoolean(1); } @@ -288,34 +285,33 @@ public class XnatExpiredPasswordFilter extends GenericFilterBean { if (!passwordExpirationDirtied) { return passwordExpirationDisabled; } - if(useSiteConfigPrefs){ - final String type = XDAT.getSiteConfigPreferences().getPasswordExpirationType(); + if (useSiteConfigPrefs) { + final String type = _preferences.getPasswordExpirationType(); if (StringUtils.isBlank(type)) { passwordExpirationDisabled = true; } else if (type.equals("Interval")) { passwordExpirationInterval = true; - passwordExpirationSetting = XDAT.getSiteConfigPreferences().getPasswordExpirationInterval(); + passwordExpirationSetting = _preferences.getPasswordExpirationInterval(); passwordExpirationDisabled = passwordExpirationSetting.equals("0"); } else if (type.equals("Date")) { passwordExpirationInterval = false; - passwordExpirationSetting = Long.toString((new Date(XDAT.getSiteConfigPreferences().getPasswordExpirationDate())).getTime()); + passwordExpirationSetting = convertDateToLongString(_preferences.getPasswordExpirationDate()); passwordExpirationDisabled = passwordExpirationSetting.equals("0"); } else { passwordExpirationDisabled = true; } - } - else{ - final String type = _initializerPreferences.getPasswordExpirationType(); + } else { + final String type = _preferences.getPasswordExpirationType(); if (StringUtils.isBlank(type)) { passwordExpirationDisabled = true; } else if (type.equals("Interval")) { passwordExpirationInterval = true; - passwordExpirationSetting = _initializerPreferences.getPasswordExpirationInterval(); - passwordExpirationDisabled = StringUtils.equals(passwordExpirationSetting,"0"); + passwordExpirationSetting = _preferences.getPasswordExpirationInterval(); + passwordExpirationDisabled = StringUtils.equals(passwordExpirationSetting, "0"); } else if (type.equals("Date")) { passwordExpirationInterval = false; - passwordExpirationSetting = Long.toString((new Date(_initializerPreferences.getPasswordExpirationDate())).getTime()); - passwordExpirationDisabled = StringUtils.equals(passwordExpirationSetting,"0"); + passwordExpirationSetting = convertDateToLongString(_preferences.getPasswordExpirationDate()); + passwordExpirationDisabled = StringUtils.equals(passwordExpirationSetting, "0"); } else { passwordExpirationDisabled = true; } @@ -325,6 +321,14 @@ public class XnatExpiredPasswordFilter extends GenericFilterBean { return passwordExpirationDisabled; } + private String convertDateToLongString(final String date) throws SiteConfigurationException { + try { + return Long.toString(FORMATTER.parse(date).getTime()); + } catch (ParseException e) { + throw new SiteConfigurationException(NrgServiceError.ConfigurationError, "The password expiration date \"" + date + "\" is in an invalid format. This should use the format: " + FORMATTER.toString(), e); + } + } + private boolean isPasswordExpirationInterval() { return passwordExpirationInterval; } @@ -347,32 +351,25 @@ public class XnatExpiredPasswordFilter extends GenericFilterBean { } } - // We have to do this because the filter must be created in the root context, but the token service must be created - // in the application context. We use the context service to cheat across these boundaries. - private AliasTokenService getAliasTokenService() { - if (_aliasTokenService == null) { - _aliasTokenService = _contextService.getBean(AliasTokenService.class); - } - return _aliasTokenService; - } - - public void refreshFromSiteConfig(){ - useSiteConfigPrefs = true; - passwordExpirationDirtied = true; - } + private static final SimpleDateFormat FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss a"); private boolean useSiteConfigPrefs = false; - @Autowired - @Lazy - private InitializerSiteConfiguration _initializerPreferences; - - @Autowired - @Lazy - private ContextService _contextService; - - @Inject - private DataSource _dataSource; + private String changePasswordPath = ""; + private String changePasswordDestination = ""; + private String logoutDestination = ""; + private String loginPath = ""; + private String loginDestination = ""; + private String inactiveAccountPath; + private String inactiveAccountDestination; + private String emailVerificationDestination; + private String emailVerificationPath; + private boolean passwordExpirationDirtied = true; + private boolean passwordExpirationDisabled; + private boolean passwordExpirationInterval; + private String passwordExpirationSetting; - private AliasTokenService _aliasTokenService; + private final SiteConfigPreferences _preferences; + private final JdbcTemplate _jdbcTemplate; + private final AliasTokenService _aliasTokenService; } diff --git a/src/main/java/org/nrg/xnat/security/XnatInitCheckFilter.java b/src/main/java/org/nrg/xnat/security/XnatInitCheckFilter.java index 034c36c51d915a38c084bc2f138d06f2752482ac..8bc71dca80b2a2d09e5efa09a1de514e967a4de8 100644 --- a/src/main/java/org/nrg/xnat/security/XnatInitCheckFilter.java +++ b/src/main/java/org/nrg/xnat/security/XnatInitCheckFilter.java @@ -17,9 +17,9 @@ import org.nrg.xnat.services.XnatAppInfo; import org.nrg.xnat.utils.XnatHttpUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.filter.GenericFilterBean; -import javax.inject.Inject; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; @@ -32,6 +32,11 @@ import java.util.List; import java.util.regex.Pattern; public class XnatInitCheckFilter extends GenericFilterBean { + @Autowired + public XnatInitCheckFilter(final XnatAppInfo appInfo) { + super(); + _appInfo = appInfo; + } @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { @@ -136,8 +141,7 @@ public class XnatInitCheckFilter extends GenericFilterBean { private static Logger _log = LoggerFactory.getLogger(XnatInitCheckFilter.class); - @Inject - private XnatAppInfo _appInfo; + private final XnatAppInfo _appInfo; private String _configurationPath; private String _nonAdminErrorPath; diff --git a/src/main/java/org/nrg/xnat/security/XnatLdapContextMapper.java b/src/main/java/org/nrg/xnat/security/XnatLdapContextMapper.java index 73ab1f2855f3e8335a0d45332a6f24fa81fe5d1a..7ff5be8e9b3c1d70bba730205571e8d0d30e9fee 100644 --- a/src/main/java/org/nrg/xnat/security/XnatLdapContextMapper.java +++ b/src/main/java/org/nrg/xnat/security/XnatLdapContextMapper.java @@ -17,18 +17,17 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.ldap.userdetails.UserDetailsContextMapper; -import javax.inject.Inject; import java.util.Collection; public class XnatLdapContextMapper implements UserDetailsContextMapper { - public XnatLdapContextMapper() { - super(); - _authMethodId = ""; + public XnatLdapContextMapper(final XdatUserAuthService service) { + this(service, ""); } - public XnatLdapContextMapper(String authMethodId) { + public XnatLdapContextMapper(final XdatUserAuthService service, final String authMethodId) { super(); + _service = service; _authMethodId = authMethodId; } @@ -43,8 +42,7 @@ public class XnatLdapContextMapper implements UserDetailsContextMapper { throw new UnsupportedOperationException("LdapUserDetailsMapper only supports reading from a context."); } - @Inject - private XdatUserAuthService _service; + private final XdatUserAuthService _service; private final String _authMethodId; } \ No newline at end of file diff --git a/src/main/java/org/nrg/xnat/security/XnatLogoutHandler.java b/src/main/java/org/nrg/xnat/security/XnatLogoutHandler.java index e51fd586287fd1ada6cb97326ef3f8f00e5fa475..a5a86af44191de4207e86064ff50fedb6b0e5b46 100644 --- a/src/main/java/org/nrg/xnat/security/XnatLogoutHandler.java +++ b/src/main/java/org/nrg/xnat/security/XnatLogoutHandler.java @@ -10,17 +10,23 @@ */ package org.nrg.xnat.security; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.core.session.SessionInformation; import org.springframework.security.core.session.SessionRegistry; import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; -import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class XnatLogoutHandler extends SecurityContextLogoutHandler implements LogoutHandler { + @Autowired + public XnatLogoutHandler(final SessionRegistry registry) { + super(); + _registry = registry; + } + @Override public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { super.logout(request, response, authentication); @@ -35,7 +41,6 @@ public class XnatLogoutHandler extends SecurityContextLogoutHandler implements L } - @Inject - private SessionRegistry _registry; + private final SessionRegistry _registry; } diff --git a/src/main/java/org/nrg/xnat/security/XnatProviderManager.java b/src/main/java/org/nrg/xnat/security/XnatProviderManager.java index faca2363f135c38f9beed5c69d82ed3d441bb643..dcd35888412581ed145c64a36e82cbaaee41e377 100644 --- a/src/main/java/org/nrg/xnat/security/XnatProviderManager.java +++ b/src/main/java/org/nrg/xnat/security/XnatProviderManager.java @@ -18,12 +18,10 @@ import org.apache.commons.logging.LogFactory; import org.apache.velocity.VelocityContext; import org.hibernate.exception.DataException; import org.nrg.config.exceptions.SiteConfigurationException; -import org.nrg.framework.services.ContextService; import org.nrg.xdat.XDAT; import org.nrg.xdat.entities.AliasToken; import org.nrg.xdat.entities.UserAuthI; import org.nrg.xdat.entities.XdatUserAuth; -import org.nrg.xdat.preferences.InitializerSiteConfiguration; import org.nrg.xdat.preferences.SiteConfigPreferences; import org.nrg.xdat.security.helpers.Users; import org.nrg.xdat.services.XdatUserAuthService; @@ -37,10 +35,6 @@ import org.nrg.xnat.security.provider.XnatDatabaseAuthenticationProvider; import org.nrg.xnat.security.provider.XnatLdapAuthenticationProvider; import org.nrg.xnat.security.tokens.XnatDatabaseUsernamePasswordAuthenticationToken; import org.nrg.xnat.security.tokens.XnatLdapUsernamePasswordAuthenticationToken; -import org.nrg.xnat.security.userdetailsservices.XnatDatabaseUserDetailsService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Lazy; import org.springframework.context.support.MessageSourceAccessor; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; @@ -48,28 +42,30 @@ import org.springframework.security.authentication.*; import org.springframework.security.core.*; import org.springframework.security.core.authority.SimpleGrantedAuthority; -import javax.inject.Inject; import javax.sql.DataSource; import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; public class XnatProviderManager extends ProviderManager { - public XnatProviderManager(final List<AuthenticationProvider> providers) { + public XnatProviderManager(final List<AuthenticationProvider> providers, final XdatUserAuthService userAuthService, final AnonymousAuthenticationProvider anonymousAuthenticationProvider, final DataSource dataSource) { super(providers); + _userAuthService = userAuthService; + _anonymousAuthenticationProvider = anonymousAuthenticationProvider; + _dataSource = dataSource; } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { - Class<? extends Authentication> toTest = authentication.getClass(); - AuthenticationException lastException = null; - Authentication result = null; - List<AuthenticationProvider> providers = new ArrayList<>(); + Class<? extends Authentication> toTest = authentication.getClass(); + AuthenticationException lastException = null; + Authentication result = null; + List<AuthenticationProvider> providers = new ArrayList<>(); // HACK: This is a hack to work around open XNAT auth issue. If this is a bare un/pw auth token, use anon auth. if (authentication.getClass() == UsernamePasswordAuthenticationToken.class && authentication.getName().equalsIgnoreCase("guest")) { - providers.add(getAnonymousAuthenticationProvider()); - authentication = new AnonymousAuthenticationToken(getAnonymousAuthenticationProvider().getKey(), authentication.getPrincipal(), Collections.<GrantedAuthority> singletonList(new SimpleGrantedAuthority("ROLE_ANONYMOUS"))); + providers.add(_anonymousAuthenticationProvider); + authentication = new AnonymousAuthenticationToken(_anonymousAuthenticationProvider.getKey(), authentication.getPrincipal(), Collections.<GrantedAuthority>singletonList(new SimpleGrantedAuthority("ROLE_ANONYMOUS"))); } else { for (AuthenticationProvider candidate : getProviders()) { if (!candidate.supports(toTest)) { @@ -89,7 +85,7 @@ public class XnatProviderManager extends ProviderManager { if (((XnatDatabaseAuthenticationProvider) candidate).isPlainText()) { String username = authentication.getPrincipal().toString(); @SuppressWarnings({"SqlDialectInspection", "SqlNoDataSourceInspection"}) - final Boolean encrypted = new JdbcTemplate(_dataSource).query("SELECT primary_password_encrypt<>0 OR (primary_password_encrypt IS NULL AND CHAR_LENGTH(primary_password)=64) FROM xdat_user WHERE login=? LIMIT 1", new String[] {username}, new RowMapper<Boolean>() { + final Boolean encrypted = new JdbcTemplate(_dataSource).query("SELECT primary_password_encrypt<>0 OR (primary_password_encrypt IS NULL AND CHAR_LENGTH(primary_password)=64) FROM xdat_user WHERE login=? LIMIT 1", new String[]{username}, new RowMapper<Boolean>() { public Boolean mapRow(ResultSet rs, int rowNum) throws SQLException { return rs.getBoolean(1); } @@ -142,26 +138,22 @@ public class XnatProviderManager extends ProviderManager { ((CredentialsContainer) result).eraseCredentials(); } - eventPublisher.publishAuthenticationSuccess(authentication); + _eventPublisher.publishAuthenticationSuccess(authentication); return result; } else { // Parent was null, or didn't authenticate (or throw an exception). if (lastException == null) { - lastException = new ProviderNotFoundException(messages.getMessage("ProviderManager.providerNotFound", - new Object[] {toTest.getName()}, "No AuthenticationProvider found for {0}")); + final String message = messages.getMessage("providerManager.providerNotFound", new Object[]{toTest.getName()}, "No authentication provider found for {0}"); + lastException = new ProviderNotFoundException(message); } - eventPublisher.publishAuthenticationFailure(lastException, authentication); + _eventPublisher.publishAuthenticationFailure(lastException, authentication); throw lastException; } } - private AnonymousAuthenticationProvider getAnonymousAuthenticationProvider() { - return _contextService.getBean(AnonymousAuthenticationProvider.class); - } - public XdatUserAuth getUserByAuth(Authentication authentication) { if (authentication == null) { return null; @@ -179,12 +171,12 @@ public class XnatProviderManager extends ProviderManager { provider = ((XnatLdapUsernamePasswordAuthenticationToken) authentication).getProviderId(); method = XdatUserAuthService.LDAP; } else { - provider = XnatDatabaseUserDetailsService.DB_PROVIDER; + provider = ""; method = XdatUserAuthService.LOCALDB; } try { - return getUserAuthService().getUserByNameAndAuth(u, method, provider); + return _userAuthService.getUserByNameAndAuth(u, method, provider); } catch (DataException exception) { _log.error("An error occurred trying to retrieve the auth method", exception); throw new RuntimeException("An error occurred trying to validate the given information. Please check your username and password. If this problem persists, please contact your system administrator."); @@ -205,7 +197,7 @@ public class XnatProviderManager extends ProviderManager { String auth = cached_methods.get(username); if (auth == null) { try { - List<XdatUserAuth> userAuths = getUserAuthService().getUsersByName(username); + List<XdatUserAuth> userAuths = _userAuthService.getUsersByName(username); if (userAuths.size() == 1) { auth = userAuths.get(0).getAuthMethod(); cached_methods.put(username.intern(), auth.intern()); @@ -230,13 +222,6 @@ public class XnatProviderManager extends ProviderManager { return auth; } - private XdatUserAuthService getUserAuthService() { - if (_userAuthService == null) { - _userAuthService = _contextService.getBean(XdatUserAuthService.class); - } - return _userAuthService; - } - private static UsernamePasswordAuthenticationToken buildUPToken(XnatAuthenticationProvider provider, String username, String password) { if (provider instanceof XnatLdapAuthenticationProvider) { return new XnatLdapUsernamePasswordAuthenticationToken(username, password, provider.getProviderId()); @@ -268,9 +253,11 @@ public class XnatProviderManager extends ProviderManager { private XnatAuthenticationProvider findAuthenticationProvider(XnatAuthenticationProviderMatcher matcher) { List<AuthenticationProvider> prov = getProviders(); for (AuthenticationProvider ap : prov) { - XnatAuthenticationProvider xap = (XnatAuthenticationProvider) ap; - if (matcher.matches(xap)) { - return xap; + if(XnatAuthenticationProvider.class.isAssignableFrom(ap.getClass())) { + XnatAuthenticationProvider xap = (XnatAuthenticationProvider) ap; + if (matcher.matches(xap)) { + return xap; + } } } return null; @@ -390,19 +377,9 @@ public class XnatProviderManager extends ProviderManager { protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); - @Inject - private DataSource _dataSource; - - @Autowired - @Qualifier("rootContextService") - @Lazy - private ContextService _contextService; - - @Autowired - @Lazy - private InitializerSiteConfiguration _preferences; - - private XdatUserAuthService _userAuthService; + private final AuthenticationEventPublisher _eventPublisher = new AuthenticationAttemptEventPublisher(this); - private final AuthenticationEventPublisher eventPublisher = new AuthenticationAttemptEventPublisher(this); + private final XdatUserAuthService _userAuthService; + private final AnonymousAuthenticationProvider _anonymousAuthenticationProvider; + private final DataSource _dataSource; } diff --git a/src/main/java/org/nrg/xnat/security/XnatSessionEventPublisher.java b/src/main/java/org/nrg/xnat/security/XnatSessionEventPublisher.java index 5fec5247ff826c996dd060ee30cd9168bf66855b..a9a6cde795a6f83878ec7578c798574c112dbf58 100644 --- a/src/main/java/org/nrg/xnat/security/XnatSessionEventPublisher.java +++ b/src/main/java/org/nrg/xnat/security/XnatSessionEventPublisher.java @@ -15,6 +15,7 @@ import org.nrg.xdat.preferences.SiteConfigPreferences; import org.nrg.xft.security.UserI; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.security.authentication.AnonymousAuthenticationToken; @@ -25,7 +26,6 @@ import org.springframework.security.web.session.HttpSessionCreatedEvent; import org.springframework.security.web.session.HttpSessionDestroyedEvent; import org.springframework.web.context.support.WebApplicationContextUtils; -import javax.inject.Inject; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; @@ -46,6 +46,7 @@ public class XnatSessionEventPublisher implements HttpSessionListener, ServletCo * * @param event HttpSessionEvent passed in by the container */ + @Override public void sessionCreated(HttpSessionEvent event) { HttpSession session = event.getSession(); HttpSessionCreatedEvent e = new HttpSessionCreatedEvent(session); @@ -69,6 +70,7 @@ public class XnatSessionEventPublisher implements HttpSessionListener, ServletCo * * @param event The HttpSessionEvent pass in by the container */ + @Override public void sessionDestroyed(final HttpSessionEvent event) { final String sessionId = event.getSession().getId(); final Date today = Calendar.getInstance(TimeZone.getDefault()).getTime(); @@ -115,12 +117,16 @@ public class XnatSessionEventPublisher implements HttpSessionListener, ServletCo } } + @Autowired + public void setJdbcTemplate(final JdbcTemplate template) { + _template = template; + } + private ApplicationContext getContext(ServletContext servletContext) { return WebApplicationContextUtils.findWebApplicationContext(servletContext); // contextAttribute in xnat's case will always be "org.springframework.web.servlet.FrameworkServlet.CONTEXT.spring-mvc"); } private static final Logger _log = LoggerFactory.getLogger(XnatSessionEventPublisher.class); - @Inject private JdbcTemplate _template; } \ No newline at end of file diff --git a/src/main/java/org/nrg/xnat/security/alias/AliasTokenAuthenticationProvider.java b/src/main/java/org/nrg/xnat/security/alias/AliasTokenAuthenticationProvider.java index e8a9a051dfac89496088429d4da8aeab0f8a7550..51840c2d3f878a0ce23cd1646345e005be9a607e 100644 --- a/src/main/java/org/nrg/xnat/security/alias/AliasTokenAuthenticationProvider.java +++ b/src/main/java/org/nrg/xnat/security/alias/AliasTokenAuthenticationProvider.java @@ -11,17 +11,14 @@ package org.nrg.xnat.security.alias; import org.apache.commons.lang3.StringUtils; -import org.nrg.framework.services.ContextService; import org.nrg.xdat.entities.AliasToken; import org.nrg.xdat.security.helpers.Users; import org.nrg.xdat.services.AliasTokenService; import org.nrg.xdat.services.XdatUserAuthService; -import org.nrg.xdat.services.impl.hibernate.HibernateAliasTokenService; import org.nrg.xft.security.UserI; import org.nrg.xnat.security.provider.XnatAuthenticationProvider; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Lazy; +import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -32,31 +29,27 @@ import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; public class AliasTokenAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider implements XnatAuthenticationProvider { - - public AliasTokenAuthenticationProvider() { - this("token"); - } - - public AliasTokenAuthenticationProvider(final String name) { - _name = name; + @Autowired + public AliasTokenAuthenticationProvider(final AliasTokenService aliasTokenService, final XdatUserAuthService userAuthService) { + super(); + _aliasTokenService = aliasTokenService; + _userAuthService = userAuthService; } /** - * Performs authentication with the same contract as {@link - * org.springframework.security.authentication.AuthenticationManager#authenticate(org.springframework.security.core.Authentication)}. + * Performs authentication with the same contract as {@link AuthenticationManager#authenticate(Authentication)}. * * @param authentication the authentication request object. * @return a fully authenticated object including credentials. May return <code>null</code> if the - * <code>AuthenticationProvider</code> is unable to support authentication of the passed - * <code>Authentication</code> object. In such a case, the next <code>AuthenticationProvider</code> that - * supports the presented <code>Authentication</code> class will be tried. - * @throws org.springframework.security.core.AuthenticationException - * if authentication fails. + * <code>AuthenticationProvider</code> is unable to support authentication of the passed + * <code>Authentication</code> object. In such a case, the next <code>AuthenticationProvider</code> that + * supports the presented <code>Authentication</code> class will be tried. + * @throws AuthenticationException if authentication fails. */ @Override public Authentication authenticate(final Authentication authentication) throws AuthenticationException { - final String alias = (String) authentication.getPrincipal(); - final AliasToken token = getAliasTokenService().locateToken(alias); + final String alias = (String) authentication.getPrincipal(); + final AliasToken token = _aliasTokenService.locateToken(alias); if (token == null) { throw new BadCredentialsException("No valid alias token found for alias: " + alias); } @@ -79,7 +72,7 @@ public class AliasTokenAuthenticationProvider extends AbstractUserDetailsAuthent * * @param authentication DOCUMENT ME! * @return <code>true</code> if the implementation can more closely evaluate the <code>Authentication</code> class - * presented + * presented */ @Override public boolean supports(final Class<?> authentication) { @@ -99,16 +92,12 @@ public class AliasTokenAuthenticationProvider extends AbstractUserDetailsAuthent @Override public String getName() { - return StringUtils.isBlank(_name) ? getClass().toString() : _name; + return XdatUserAuthService.TOKEN; } @Override public String getProviderId() { - return _providerId; - } - - public void setProviderId(final String id) { - _providerId = id; + return XdatUserAuthService.TOKEN; } @Override @@ -121,6 +110,16 @@ public class AliasTokenAuthenticationProvider extends AbstractUserDetailsAuthent return getName(); } + @Override + public int getOrder() { + return _order; + } + + @Override + public void setOrder(int order) { + _order = order; + } + @Override protected void additionalAuthenticationChecks(final UserDetails userDetails, final UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { if (authentication.getCredentials() == null) { @@ -134,9 +133,9 @@ public class AliasTokenAuthenticationProvider extends AbstractUserDetailsAuthent final UserI xdatUserDetails = (UserI) userDetails; Users.validateUserLogin(xdatUserDetails); - String alias = ((AliasTokenAuthenticationToken) authentication).getAlias(); + String alias = ((AliasTokenAuthenticationToken) authentication).getAlias(); String secret = ((AliasTokenAuthenticationToken) authentication).getSecret(); - String userId = getAliasTokenService().validateToken(alias, secret); + String userId = _aliasTokenService.validateToken(alias, secret); if (StringUtils.isBlank(userId) || !userId.equals(userDetails.getUsername())) { throw new BadCredentialsException("The submitted alias token was invalid: " + alias); } @@ -164,14 +163,13 @@ public class AliasTokenAuthenticationProvider extends AbstractUserDetailsAuthent * @param authentication The authentication request, which subclasses <em>may</em> need to perform a binding-based * retrieval of the <code>UserDetails</code> * @return the user information (never <code>null</code> - instead an exception should the thrown) - * @throws org.springframework.security.core.AuthenticationException - * if the credentials could not be validated (generally a - * <code>BadCredentialsException</code>, an <code>AuthenticationServiceException</code> or - * <code>UsernameNotFoundException</code>) + * @throws AuthenticationException If the credentials could not be validated (generally a + * <code>BadCredentialsException</code>, an <code>AuthenticationServiceException</code> or + * <code>UsernameNotFoundException</code>) */ @Override protected UserDetails retrieveUser(final String username, final UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { - AliasToken token = getAliasTokenService().locateToken(username); + AliasToken token = _aliasTokenService.locateToken(username); if (token == null) { throw new UsernameNotFoundException("Unable to locate token with alias: " + username); } @@ -180,32 +178,10 @@ public class AliasTokenAuthenticationProvider extends AbstractUserDetailsAuthent * The hack is to return the user details for the most recent successful login of the user, as that is likely the provider that was used. * Not perfect, but better than just hard-coding to localdb provider (cause then it won't work for a token created by an LDAP-authenticated user). */ - return getUserAuthService().getUserDetailsByUsernameAndMostRecentSuccessfulLogin(token.getXdatUserId()); - } - - private XdatUserAuthService getUserAuthService() { - if (_userAuthService == null) { - _userAuthService = _contextService.getBean(XdatUserAuthService.class); - } - return _userAuthService; - } - - private AliasTokenService getAliasTokenService() { - if (_aliasTokenService == null) { - _aliasTokenService = _contextService.getBean(HibernateAliasTokenService.class); - } - return _aliasTokenService; + return _userAuthService.getUserDetailsByUsernameAndMostRecentSuccessfulLogin(token.getXdatUserId()); } - @Autowired - @Qualifier("rootContextService") - @Lazy - private ContextService _contextService; - - private AliasTokenService _aliasTokenService; - - private XdatUserAuthService _userAuthService; - - private final String _name; - private String _providerId; + private final AliasTokenService _aliasTokenService; + private final XdatUserAuthService _userAuthService; + private int _order = -1; } diff --git a/src/main/java/org/nrg/xnat/security/config/AuthenticationProviderAggregator.java b/src/main/java/org/nrg/xnat/security/config/AuthenticationProviderAggregator.java index 6990368e825463d43f11210b3534c5b69b015cc4..f7ac4ba691be2aaf1910adcbd42410287533d5ed 100644 --- a/src/main/java/org/nrg/xnat/security/config/AuthenticationProviderAggregator.java +++ b/src/main/java/org/nrg/xnat/security/config/AuthenticationProviderAggregator.java @@ -2,6 +2,7 @@ package org.nrg.xnat.security.config; import org.apache.commons.lang3.StringUtils; import org.nrg.framework.utilities.BasicXnatResourceLocator; +import org.nrg.xnat.security.provider.XnatAuthenticationProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.io.Resource; @@ -13,29 +14,33 @@ import java.util.*; public class AuthenticationProviderAggregator extends ArrayList<AuthenticationProvider> { public AuthenticationProviderAggregator(List<AuthenticationProvider> standaloneProviders, Map<String, AuthenticationProviderConfigurator> configurators) { - ArrayList<String> providerArray = new ArrayList<>(); - String dbName = "Database"; - String dbId = "localdb"; - String dbType = "db"; - providerArray.add(dbType); - HashMap<String, HashMap<String, String>> providerMap = new HashMap<>(); - providerMap.put(dbType, new HashMap<String, String>()); - providerMap.get(dbType).put("name", dbName); - providerMap.get(dbType).put("id", dbId); - providerMap.get(dbType).put("type", dbType); + ArrayList<HashMap<String, String>> providerList = new ArrayList<>(); // Populate map of properties try { String filenameEnd = "-provider.properties"; final List<Resource> resources = BasicXnatResourceLocator.getResources("classpath*:META-INF/xnat/auth/**/*" + filenameEnd); - for (final Resource resource : resources) { - String filename = resource.getFilename(); - String id = filename.substring(0, (filename.length() - filenameEnd.length())); - providerMap.put(id, new HashMap<String, String>()); - providerArray.add(id); - final Properties provider = PropertiesLoaderUtils.loadProperties(resource); - for (Map.Entry<Object, Object> providerProperty : provider.entrySet()) { - providerMap.get(id).put(providerProperty.getKey().toString(), providerProperty.getValue().toString()); + if(resources==null || resources.isEmpty()){ + String dbName = "Database"; + String dbId = "localdb"; + String dbType = "db"; + HashMap<String, String> dbProv = new HashMap<String, String>(); + dbProv.put("name", dbName); + dbProv.put("id", dbId); + dbProv.put("type", dbType); + providerList.add(dbProv); + } + else { + for (final Resource resource : resources) { + String filename = resource.getFilename(); + String id = filename.substring(0, (filename.length() - filenameEnd.length())); + HashMap<String, String> newProv = new HashMap<String, String>(); + + final Properties provider = PropertiesLoaderUtils.loadProperties(resource); + for (Map.Entry<Object, Object> providerProperty : provider.entrySet()) { + newProv.put(providerProperty.getKey().toString(), providerProperty.getValue().toString()); + } + providerList.add(newProv); } } } catch (Exception e) { @@ -43,10 +48,10 @@ public class AuthenticationProviderAggregator extends ArrayList<AuthenticationPr } // Create providers - for (String prov : providerArray) { - String name = providerMap.get(prov).get("name"); - String id = providerMap.get(prov).get("id"); - String type = providerMap.get(prov).get("type"); + for (HashMap<String, String> prov : providerList) { + String name = prov.get("name"); + String id = prov.get("id"); + String type = prov.get("type"); assert !StringUtils.isBlank(name) : "You must provide a name for all authentication provider configurations"; assert !StringUtils.isBlank(id) : "You must provide an ID for all authentication provider configurations"; @@ -54,13 +59,37 @@ public class AuthenticationProviderAggregator extends ArrayList<AuthenticationPr if (configurators.containsKey(type)) { AuthenticationProviderConfigurator configurator = configurators.get(type); - addAll(configurator.getAuthenticationProviders(id, name, providerMap.get(prov))); + + addAll(configurator.getAuthenticationProviders(id, name, prov)); } } if (standaloneProviders != null) { addAll(standaloneProviders); } + + Collections.sort(this, new Comparator<AuthenticationProvider>(){ + public int compare(AuthenticationProvider o1, AuthenticationProvider o2){ + if(XnatAuthenticationProvider.class.isAssignableFrom(o1.getClass())){ + if(XnatAuthenticationProvider.class.isAssignableFrom(o2.getClass())){ + if(((XnatAuthenticationProvider)o1).getOrder() == ((XnatAuthenticationProvider)o2).getOrder()) + return 0; + return ((XnatAuthenticationProvider)o1).getOrder() < ((XnatAuthenticationProvider)o2).getOrder() ? -1 : 1; + } + else{ + return 1; + } + } + else{ + if(XnatAuthenticationProvider.class.isAssignableFrom(o2.getClass())){ + return -1; + } + else{ + return 0; + } + } + } + }); } private static final Logger _log = LoggerFactory.getLogger(AuthenticationProviderAggregator.class); diff --git a/src/main/java/org/nrg/xnat/security/config/DatabaseAuthenticationProviderConfigurator.java b/src/main/java/org/nrg/xnat/security/config/DatabaseAuthenticationProviderConfigurator.java index 0857caa31dba50c47e834af3cc7d217a7c3ab4d1..f9fba0b3c81bf01c0ea61873ed38cfa2f8252486 100644 --- a/src/main/java/org/nrg/xnat/security/config/DatabaseAuthenticationProviderConfigurator.java +++ b/src/main/java/org/nrg/xnat/security/config/DatabaseAuthenticationProviderConfigurator.java @@ -10,11 +10,11 @@ */ package org.nrg.xnat.security.config; -import org.nrg.xdat.preferences.InitializerSiteConfiguration; +import org.nrg.xdat.preferences.SiteConfigPreferences; +import org.nrg.xnat.security.provider.XnatAuthenticationProvider; import org.nrg.xnat.security.provider.XnatDatabaseAuthenticationProvider; import org.nrg.xnat.security.userdetailsservices.XnatDatabaseUserDetailsService; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.dao.ReflectionSaltSource; import org.springframework.security.authentication.encoding.ShaPasswordEncoder; @@ -24,7 +24,11 @@ import java.util.List; import java.util.Map; public class DatabaseAuthenticationProviderConfigurator extends AbstractAuthenticationProviderConfigurator { - public DatabaseAuthenticationProviderConfigurator() { + @Autowired + public DatabaseAuthenticationProviderConfigurator(final XnatDatabaseUserDetailsService userDetailsService, final SiteConfigPreferences preferences) { + super(); + _userDetailsService = userDetailsService; + _preferences = preferences; setConfiguratorId("db"); } @@ -36,7 +40,7 @@ public class DatabaseAuthenticationProviderConfigurator extends AbstractAuthenti saltSource.setUserPropertyToUse("salt"); XnatDatabaseAuthenticationProvider sha2DatabaseAuthProvider = new XnatDatabaseAuthenticationProvider(_preferences.getEmailVerification()); - sha2DatabaseAuthProvider.setUserDetailsService(_detailsService); + sha2DatabaseAuthProvider.setUserDetailsService(_userDetailsService); sha2DatabaseAuthProvider.setPasswordEncoder(new ShaPasswordEncoder(256)); sha2DatabaseAuthProvider.setName(name); sha2DatabaseAuthProvider.setProviderId(id); @@ -48,13 +52,17 @@ public class DatabaseAuthenticationProviderConfigurator extends AbstractAuthenti @Override public List<AuthenticationProvider> getAuthenticationProviders(String id, String name, Map<String, String> properties) { - return getAuthenticationProviders(id, name); + List<AuthenticationProvider> provs = getAuthenticationProviders(id, name); + for(AuthenticationProvider prov : provs){ + if(XnatAuthenticationProvider.class.isAssignableFrom(prov.getClass())){ + if (properties.get("order") != null) { + ((XnatAuthenticationProvider)prov).setOrder(Integer.parseInt(properties.get("order"))); + } + } + } + return provs; } - @Autowired - @Lazy - private XnatDatabaseUserDetailsService _detailsService; - - @Autowired - private InitializerSiteConfiguration _preferences; + private final XnatDatabaseUserDetailsService _userDetailsService; + private final SiteConfigPreferences _preferences; } diff --git a/src/main/java/org/nrg/xnat/security/config/LdapAuthenticationProviderConfigurator.java b/src/main/java/org/nrg/xnat/security/config/LdapAuthenticationProviderConfigurator.java index a23300a67337716d01073f5c1c0cc419cc5799ab..1956761fe2fc80521c3d3f61ef62e9440748be64 100644 --- a/src/main/java/org/nrg/xnat/security/config/LdapAuthenticationProviderConfigurator.java +++ b/src/main/java/org/nrg/xnat/security/config/LdapAuthenticationProviderConfigurator.java @@ -19,7 +19,6 @@ import org.nrg.xnat.security.XnatLdapAuthoritiesPopulator; import org.nrg.xnat.security.XnatLdapUserDetailsMapper; import org.nrg.xnat.security.provider.XnatLdapAuthenticationProvider; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.ldap.DefaultSpringSecurityContextSource; import org.springframework.security.ldap.authentication.BindAuthenticator; @@ -30,8 +29,12 @@ import java.util.List; import java.util.Map; public class LdapAuthenticationProviderConfigurator extends AbstractAuthenticationProviderConfigurator { - public LdapAuthenticationProviderConfigurator() { + @Autowired + public LdapAuthenticationProviderConfigurator(final XdatUserAuthService userAuthService, final SiteConfigPreferences preferences) { + super(); setConfiguratorId("ldap"); + _userAuthService = userAuthService; + _preferences = preferences; } @Override @@ -46,6 +49,9 @@ public class LdapAuthenticationProviderConfigurator extends AbstractAuthenticati ldapAuthProvider.setUserDetailsContextMapper(new XnatLdapUserDetailsMapper(id, properties, _userAuthService, _preferences)); ldapAuthProvider.setName(name); ldapAuthProvider.setProviderId(id); + if (properties.get("order") != null) { + ldapAuthProvider.setOrder(Integer.parseInt(properties.get("order"))); + } return Arrays.asList(new AuthenticationProvider[] { ldapAuthProvider }); } catch (Exception exception) { _log.error("Something went wrong when configuring the LDAP authentication provider", exception); @@ -69,11 +75,6 @@ public class LdapAuthenticationProviderConfigurator extends AbstractAuthenticati private static final Log _log = LogFactory.getLog(LdapAuthenticationProviderConfigurator.class); - @Autowired - @Lazy - private XdatUserAuthService _userAuthService; - - @Autowired - @Lazy - private SiteConfigPreferences _preferences; + private final XdatUserAuthService _userAuthService; + private final SiteConfigPreferences _preferences; } diff --git a/src/main/java/org/nrg/xnat/security/provider/XnatAuthenticationProvider.java b/src/main/java/org/nrg/xnat/security/provider/XnatAuthenticationProvider.java index a54351af2f89f895d6f871b44b5236734f810942..a23011a59a71fa9978e434825d43f9ccb7fd0926 100644 --- a/src/main/java/org/nrg/xnat/security/provider/XnatAuthenticationProvider.java +++ b/src/main/java/org/nrg/xnat/security/provider/XnatAuthenticationProvider.java @@ -18,26 +18,35 @@ public interface XnatAuthenticationProvider extends AuthenticationProvider { * the authentication method. * @return The display name for the XNAT authentication provider. */ - abstract public String getName(); + String getName(); /** * Gets the provider ID for the XNAT authentication provider. This is used to map the properties associated with the * provider instance. * @return The provider ID for the XNAT authentication provider. */ - abstract public String getProviderId(); + String getProviderId(); /** * Indicates whether the provider should be visible to and selectable by users. <b>false</b> usually indicates an * internal authentication provider, e.g. token authentication. * @return <b>true</b> if the provider should be visible to and usable by users. */ - abstract public boolean isVisible(); + boolean isVisible(); /** * Indicates the authentication method associated with this provider. This is used to locate the provider based on * the user's selected authentication method. * @return The authentication method for this provider. */ - abstract public String getAuthMethod(); + String getAuthMethod(); + + /** + * Indicates the order associated with this provider. This is used to determine the order in which the providers + * show up in the login dropdown and the order in which they are checked when a login is attempted. + * @return The order for this provider. + */ + int getOrder(); + + void setOrder(int order); } diff --git a/src/main/java/org/nrg/xnat/security/provider/XnatDatabaseAuthenticationProvider.java b/src/main/java/org/nrg/xnat/security/provider/XnatDatabaseAuthenticationProvider.java index 8dac7b2415dfef434d0a4e7f8abfd0775222f286..3ad7b36ade6addefcff69153cba94673119ed416 100644 --- a/src/main/java/org/nrg/xnat/security/provider/XnatDatabaseAuthenticationProvider.java +++ b/src/main/java/org/nrg/xnat/security/provider/XnatDatabaseAuthenticationProvider.java @@ -74,6 +74,16 @@ public class XnatDatabaseAuthenticationProvider extends DaoAuthenticationProvide return XdatUserAuthService.LOCALDB; } + @Override + public int getOrder() { + return _order; + } + + @Override + public void setOrder(int order) { + _order = order; + } + @Override protected void additionalAuthenticationChecks(final UserDetails userDetails, final UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { if (!UserI.class.isAssignableFrom(userDetails.getClass())) { @@ -143,4 +153,5 @@ public class XnatDatabaseAuthenticationProvider extends DaoAuthenticationProvide } private final boolean _requireEmailVerification; + private int _order = -1; } diff --git a/src/main/java/org/nrg/xnat/security/provider/XnatLdapAuthenticationProvider.java b/src/main/java/org/nrg/xnat/security/provider/XnatLdapAuthenticationProvider.java index c57c49edcf03ece272e78e0696c10e7594423d5a..20fa4dc3bd826bf590ac2da0f4051f7253b015a5 100644 --- a/src/main/java/org/nrg/xnat/security/provider/XnatLdapAuthenticationProvider.java +++ b/src/main/java/org/nrg/xnat/security/provider/XnatLdapAuthenticationProvider.java @@ -94,6 +94,16 @@ public class XnatLdapAuthenticationProvider extends LdapAuthenticationProvider i return XdatUserAuthService.LDAP; } + @Override + public int getOrder() { + return _order; + } + + @Override + public void setOrder(int order) { + _order = order; + } + /** * Indicates whether the provider should be visible to and selectable by users. <b>false</b> usually indicates an * internal authentication provider, e.g. token authentication. @@ -109,4 +119,5 @@ public class XnatLdapAuthenticationProvider extends LdapAuthenticationProvider i private String _displayName = ""; private String _providerId = ""; + private int _order = -1; } diff --git a/src/main/java/org/nrg/xnat/security/userdetailsservices/XnatDatabaseUserDetailsService.java b/src/main/java/org/nrg/xnat/security/userdetailsservices/XnatDatabaseUserDetailsService.java index a69154c7563957ec68e133e0ebd17eca50c73c01..49e7c290f4257ddfc67f671b51a214c28c77b6f8 100644 --- a/src/main/java/org/nrg/xnat/security/userdetailsservices/XnatDatabaseUserDetailsService.java +++ b/src/main/java/org/nrg/xnat/security/userdetailsservices/XnatDatabaseUserDetailsService.java @@ -8,47 +8,37 @@ */ package org.nrg.xnat.security.userdetailsservices; -import org.nrg.framework.services.ContextService; import org.nrg.xdat.services.XdatUserAuthService; import org.nrg.xnat.security.PasswordExpiredException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Lazy; import org.springframework.dao.DataAccessException; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl; -public class XnatDatabaseUserDetailsService extends JdbcDaoImpl implements UserDetailsService { +import javax.sql.DataSource; - // MIGRATION: This needs to go away and be replaced by a standard property for an XNAT user details service. - public static final String DB_PROVIDER = ""; +public class XnatDatabaseUserDetailsService extends JdbcDaoImpl implements UserDetailsService { + @Autowired + public XnatDatabaseUserDetailsService(final XdatUserAuthService userAuthService, final DataSource dataSource) { + super(); + setDataSource(dataSource); + _userAuthService = userAuthService; + } @Override - public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException, PasswordExpiredException { - UserDetails user = getXdatUserAuthService().getUserDetailsByNameAndAuth(username, XdatUserAuthService.LOCALDB, DB_PROVIDER); + public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException, DataAccessException, PasswordExpiredException { + final UserDetails user = _userAuthService.getUserDetailsByNameAndAuth(username, XdatUserAuthService.LOCALDB, ""); if (_log.isDebugEnabled()) { _log.debug("Loaded user {} by username from user-auth service.", user.getUsername()); } return user; } - private XdatUserAuthService getXdatUserAuthService() { - if (_xdatUserAuthService == null) { - _xdatUserAuthService = _contextService.getBean(XdatUserAuthService.class); - } - return _xdatUserAuthService; - } - private static final Logger _log = LoggerFactory.getLogger(XnatDatabaseUserDetailsService.class); - @Autowired - @Qualifier("rootContextService") - @Lazy - private ContextService _contextService; - - private XdatUserAuthService _xdatUserAuthService; + private final XdatUserAuthService _userAuthService; } diff --git a/src/main/java/org/nrg/xnat/services/PETTracerUtils.java b/src/main/java/org/nrg/xnat/services/PETTracerUtils.java index b3d9be6a57d5b468958232c5e1e1b294062f3502..3a1ab2a552646b24d020f8946b13427628ec1c6c 100644 --- a/src/main/java/org/nrg/xnat/services/PETTracerUtils.java +++ b/src/main/java/org/nrg/xnat/services/PETTracerUtils.java @@ -1,39 +1,35 @@ -package org.nrg.xnat.services;/* +/* * org.nrg.xnat.services.PETTracerUtils.java * XNAT http://www.xnat.org - * Copyright (c) 2013, Washington University School of Medicine + * Copyright (c) 2016, Washington University School of Medicine * All Rights Reserved * * Released under the Simplified BSD. - * - * Created 8/7/14 12:50 PM */ +package org.nrg.xnat.services; +import org.apache.commons.io.IOUtils; import org.nrg.config.entities.Configuration; import org.nrg.config.exceptions.ConfigServiceException; import org.nrg.config.services.ConfigService; import org.nrg.framework.constants.Scope; -import org.nrg.xft.XFT; +import org.nrg.framework.utilities.BasicXnatResourceLocator; import org.nrg.xnat.helpers.editscript.DicomEdit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.Resource; import org.springframework.stereotype.Service; -import javax.inject.Inject; -import java.io.File; -import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; @Service public class PETTracerUtils { - public PETTracerUtils() throws Exception { - if (_instance != null) { - throw new Exception("The PETTracerUtils service is already initialized, try calling getInstance() instead."); - } - _instance = this; - } - - public static PETTracerUtils getService() { - return _instance; + @Autowired + public PETTracerUtils(final ConfigService configService) { + _configService = configService; } public Configuration getTracerList(final String path, final Long project) { @@ -60,13 +56,15 @@ public class PETTracerUtils { _configService.replaceConfig(login, "", TOOL_NAME, path, tracerList); } - public static File getDefaultTracerList() throws FileNotFoundException { - final File def = new File(XFT.GetConfDir(), DEFAULT_TRACER_LIST); - if (def.exists()) { - return def; - } else { - throw new FileNotFoundException("Default tracer list: " + DEFAULT_TRACER_LIST + " not found in " + XFT.GetConfDir()); + public static String getDefaultTracerList() throws IOException { + final StringBuilder tracers = new StringBuilder(); + final List<Resource> resources = BasicXnatResourceLocator.getResources(DEFAULT_TRACER_LIST); + for (final Resource resource : resources) { + try (final InputStream input = resource.getInputStream()) { + tracers.append(IOUtils.readLines(input, "UTF-8")); + } } + return tracers.toString(); } // flat out stolen from DicomEdit.java @@ -90,10 +88,7 @@ public class PETTracerUtils { private static final String TOOL_NAME = "tracers"; - private static final String DEFAULT_TRACER_LIST = "PET-tracers.txt"; - - private static PETTracerUtils _instance; + private static final String DEFAULT_TRACER_LIST = "classpath*:META-INF/xnat/defaults/**/PET-tracers.txt"; - @Inject - private ConfigService _configService; + private final ConfigService _configService; } diff --git a/src/main/java/org/nrg/xnat/services/XnatAppInfo.java b/src/main/java/org/nrg/xnat/services/XnatAppInfo.java index d7235d3bbeda8f21091600bd51176ae942b6f819..edd493666fec68e05c5bf677d65e17cd06617556 100644 --- a/src/main/java/org/nrg/xnat/services/XnatAppInfo.java +++ b/src/main/java/org/nrg/xnat/services/XnatAppInfo.java @@ -25,19 +25,19 @@ import java.util.jar.Manifest; @Component public class XnatAppInfo { - public static final int MILLISECONDS_IN_A_DAY = (24 * 60 * 60 * 1000); - public static final int MILLISECONDS_IN_AN_HOUR = (60 * 60 * 1000); - public static final int MILLISECONDS_IN_A_MINUTE = (60 * 1000); - public static final DecimalFormat SECONDS_FORMAT = new DecimalFormat("##.000"); - public static final String DAYS = "days"; - public static final String HOURS = "hours"; - public static final String MINUTES = "minutes"; - public static final String SECONDS = "seconds"; - - @Autowired + private static final int MILLISECONDS_IN_A_DAY = (24 * 60 * 60 * 1000); + private static final int MILLISECONDS_IN_AN_HOUR = (60 * 60 * 1000); + private static final int MILLISECONDS_IN_A_MINUTE = (60 * 1000); + private static final DecimalFormat SECONDS_FORMAT = new DecimalFormat("##.000"); + private static final String DAYS = "days"; + private static final String HOURS = "hours"; + private static final String MINUTES = "minutes"; + private static final String SECONDS = "seconds"; + + @Inject public XnatAppInfo(final ServletContext context, final JdbcTemplate template) throws IOException { try (final InputStream input = context.getResourceAsStream("/META-INF/MANIFEST.MF")) { - final Manifest manifest = new Manifest(input); + final Manifest manifest = new Manifest(input); final Attributes attributes = manifest.getMainAttributes(); _properties.setProperty("buildNumber", attributes.getValue("Build-Number")); _properties.setProperty("buildDate", attributes.getValue("Build-Date")); @@ -97,6 +97,7 @@ public class XnatAppInfo { } } } + _template = template; } public Map<String, String> getFoundPreferences() { @@ -152,14 +153,14 @@ public class XnatAppInfo { /** * Returns the primary XNAT system properties extracted from the installed application's manifest file. These * properties are guaranteed to include the following: - * + * <p> * <ul> * <li>version</li> * <li>buildNumber</li> * <li>buildDate</li> * <li>commit</li> * </ul> - * + * <p> * There may be other properties available in the system properties and even more available through the {@link * #getSystemAttributes()} method. * @@ -169,6 +170,42 @@ public class XnatAppInfo { return (Properties) _properties.clone(); } + /** + * Gets the version of the application. + * + * @return The version of the application. + */ + public String getVersion() { + return _properties.getProperty("version"); + } + + /** + * Gets the build number of the application. + * + * @return The build number of the application. + */ + public String getBuildNumber() { + return _properties.getProperty("buildNumber"); + } + + /** + * Gets the date the application was built. + * + * @return The date the application was built. + */ + public String getBuildDate() { + return _properties.getProperty("buildDate"); + } + + /** + * Gets the commit number in the source repository from which the application was built. + * + * @return The commit number of the application. + */ + public String getCommit() { + return _properties.getProperty("commit"); + } + /** * Returns extended XNAT system attributes. * @@ -196,12 +233,12 @@ public class XnatAppInfo { * @return A map of values indicating the system uptime. */ public Map<String, String> getUptime() { - final long diff = new Date().getTime() - _startTime.getTime(); - final int days = (int) (diff / MILLISECONDS_IN_A_DAY); - final long daysRemainder = diff % MILLISECONDS_IN_A_DAY; - final int hours = (int) (daysRemainder / MILLISECONDS_IN_AN_HOUR); - final long hoursRemainder = daysRemainder % MILLISECONDS_IN_AN_HOUR; - final int minutes = (int) (hoursRemainder / MILLISECONDS_IN_A_MINUTE); + final long diff = new Date().getTime() - _startTime.getTime(); + final int days = (int) (diff / MILLISECONDS_IN_A_DAY); + final long daysRemainder = diff % MILLISECONDS_IN_A_DAY; + final int hours = (int) (daysRemainder / MILLISECONDS_IN_AN_HOUR); + final long hoursRemainder = daysRemainder % MILLISECONDS_IN_AN_HOUR; + final int minutes = (int) (hoursRemainder / MILLISECONDS_IN_A_MINUTE); final long minutesRemainder = hoursRemainder % MILLISECONDS_IN_A_MINUTE; final Map<String, String> uptime = new HashMap<>(); @@ -226,7 +263,7 @@ public class XnatAppInfo { */ public String getFormattedUptime() { final Map<String, String> uptime = getUptime(); - final StringBuilder buffer = new StringBuilder(); + final StringBuilder buffer = new StringBuilder(); if (uptime.containsKey(DAYS)) { buffer.append(uptime.get(DAYS)).append(" days, "); } @@ -245,6 +282,7 @@ public class XnatAppInfo { private static final List<String> PRIMARY_MANIFEST_ATTRIBUTES = Arrays.asList("Build-Number", "Build-Date", "Implementation-Version", "Implementation-Sha"); private final JdbcTemplate _template; + private final Map<String, String> _foundPreferences = new HashMap<>(); private final Date _startTime = new Date(); diff --git a/src/main/java/org/nrg/xnat/turbine/modules/screens/InactiveAccount.java b/src/main/java/org/nrg/xnat/turbine/modules/screens/InactiveAccount.java index f87e52952128185be8e930ceeb88a331e466a812..1dfa4a3b13e88b56c990b9802ae3b729b79f1d34 100644 --- a/src/main/java/org/nrg/xnat/turbine/modules/screens/InactiveAccount.java +++ b/src/main/java/org/nrg/xnat/turbine/modules/screens/InactiveAccount.java @@ -19,8 +19,6 @@ package org.nrg.xnat.turbine.modules.screens;/* * Created 10/29/13 12:00 PM */ -import java.sql.SQLException; - import org.apache.commons.lang3.StringEscapeUtils; import org.apache.commons.lang3.StringUtils; import org.apache.turbine.modules.screens.VelocitySecureScreen; @@ -36,6 +34,8 @@ import org.nrg.xdat.turbine.utils.AdminUtils; import org.nrg.xdat.turbine.utils.TurbineUtils; import org.nrg.xft.security.UserI; +import java.sql.SQLException; + public class InactiveAccount extends VelocitySecureScreen { @Override @@ -53,7 +53,7 @@ public class InactiveAccount extends VelocitySecureScreen { !TurbineUtils.getUser(data).getUsername().equalsIgnoreCase("guest") && !TurbineUtils.HasPassedParameter("a", data) && !TurbineUtils.HasPassedParameter("s", data)) { context.put("login", TurbineUtils.getUser(data).getUsername()); - context.put("topMessage", "Your account has been disabled due to inactivity.<br>" + + context.put("topMessage", "Your account is not currently enabled, possibly due to inactivity.<br>" + "Enter your email address to send a reactivation email."); } else { UserI user = XDAT.getUserDetails(); @@ -103,7 +103,7 @@ public class InactiveAccount extends VelocitySecureScreen { } } } - context.put("topMessage", "Your account has been disabled due to inactivity.<br>" + + context.put("topMessage", "Your account is not currently enabled, possibly due to inactivity.<br>" + "Enter your email address to resend the verification email."); } } catch (Exception e) { diff --git a/src/main/java/org/nrg/xnat/utils/XnatUserProvider.java b/src/main/java/org/nrg/xnat/utils/XnatUserProvider.java index cbc893b87a20a9876ffd26d848415b5f55772ea1..8ff70059079273182d332b8653dc1363773fe785 100644 --- a/src/main/java/org/nrg/xnat/utils/XnatUserProvider.java +++ b/src/main/java/org/nrg/xnat/utils/XnatUserProvider.java @@ -10,7 +10,11 @@ */ package org.nrg.xnat.utils; +import org.nrg.framework.exceptions.NrgServiceError; +import org.nrg.framework.exceptions.NrgServiceRuntimeException; 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.xft.security.UserI; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,28 +22,42 @@ import org.springframework.stereotype.Component; import javax.inject.Provider; +/** + * Defines the default user for XNAT services. + */ @Component public class XnatUserProvider implements Provider<UserI> { public XnatUserProvider(final String login) { _login = login; } - /* - * (non-Javadoc) - * @see javax.inject.Provider#get() + /** + * {@inheritDoc} */ + @Override public UserI get() { if (null == user) { try { user = Users.getUser(_login); - } catch (Throwable t) { - _logger.error("Unable to retrieve user " + _login, t); - return null; + } catch (UserInitException e) { + throw new NrgServiceRuntimeException(NrgServiceError.UserServiceError, "User object for name " + _login + " could not be initialized."); + } catch (UserNotFoundException e) { + throw new NrgServiceRuntimeException(NrgServiceError.UserNotFoundError, "User with name " + _login + " could not be found."); } } return user; } + /** + * Returns the configured login name for the default user. This can be used when only the username is required, + * since this is a more lightweight operation. + * + * @return The configured user login name. + */ + public String getLogin() { + return _login; + } + private final Logger _logger = LoggerFactory.getLogger(XnatUserProvider.class); private final String _login; private UserI user = null; diff --git a/src/main/resources/META-INF/xnat/defaults/PET-tracers.txt b/src/main/resources/META-INF/xnat/defaults/PET-tracers.txt new file mode 100644 index 0000000000000000000000000000000000000000..832b798619160148040c126675021f71d58fcf38 --- /dev/null +++ b/src/main/resources/META-INF/xnat/defaults/PET-tracers.txt @@ -0,0 +1,2 @@ +PIB +FDG diff --git a/src/main/resources/META-INF/xnat/defaults/id.das b/src/main/resources/META-INF/xnat/defaults/id.das new file mode 100644 index 0000000000000000000000000000000000000000..e211f1bf0ea7fc8b39502f175707ca0f055f5ac1 --- /dev/null +++ b/src/main/resources/META-INF/xnat/defaults/id.das @@ -0,0 +1,4 @@ +project = "Unassigned" : (0008,1030) := (0008,1030) +(0008,1030) := project +(0010,0010) := subject +(0010,0020) := session diff --git a/src/main/resources/META-INF/xnat/spawner/site-admin-elements.yaml b/src/main/resources/META-INF/xnat/spawner/site-admin-elements.yaml index c5df1de15c47f4fa02440ee827b66fb09ba870b2..c1d5acb1f2277b5dcb42df02dc1ed8a4b23ffd50 100644 --- a/src/main/resources/META-INF/xnat/spawner/site-admin-elements.yaml +++ b/src/main/resources/META-INF/xnat/spawner/site-admin-elements.yaml @@ -273,7 +273,7 @@ generalSecuritySettings: securityChannel: kind: panel.select.single id: securityChannel - name: security.channel + name: ":security.channel" label: Security Channel options: any: @@ -298,13 +298,13 @@ generalSecuritySettings: kind: panel.input.checkbox id: restrictUserListAccessToAdmins name: restrictUserListAccessToAdmins - label: "Restrict user list access to site administrators?" + label: "Restrict user list access <br>to site administrators?" description: "Should this site restrict access to the list of system users to site administrators only? If turned on, the site is more secure, but this restricts project owners from being able to administer users in their projects directly." uiAllowNonAdminProjectCreation: kind: panel.input.checkbox id: uiAllowNonAdminProjectCreation - name: UI.allow-non-admin-project-creation - label: "Allow non-administrators to create projects?" + name: ":UI.allow-non-admin-project-creation" + label: "Allow non-administrators <br>to create projects?" description: "Should this site allow non-administrator users to create new projects? If turned on, the site is more secure, but this can make it more difficult for regular users to create new projects for their research efforts." userLoginsSessionControls: @@ -356,16 +356,16 @@ userLoginsSessionControls: maximumConcurrentSessions: kind: panel.input.number id: maximumConcurrentSessions - name: sessions.concurrent_max + name: ":sessions.concurrent_max" label: Maximum Concurrent Sessions description: The maximum number of permitted sessions a user can have open simultaneously loginFailureMessage: kind: panel.textarea id: loginFailureMessage - name: UI.login_failure_message + name: ":UI.login_failure_message" label: Login Failure Message description: Text to show when a user fails to login - value: "?? XNAT:data:siteConfig:UI.login_failure_message" +# value: "?? XNAT:data:siteConfig:UI.login_failure_message" maximumFailedLogins: kind: panel.input.number id: maximumFailedLogins @@ -547,22 +547,22 @@ securityServices: securityServicesFeatureDefault: kind: panel.input.text id: securityServicesFeatureDefault - name: security.services.feature.default + name: ":security.services.feature.default" label: Feature Default securityServicesFeatureRepoDefault: kind: panel.input.text id: securityServicesFeatureRepoDefault - name: security.services.featureRepository.default + name: ":security.services.featureRepository.default" label: Feature Repository Default securityServicesRoleDefault: kind: panel.input.text id: securityServicesRoleDefault - name: security.services.role.default + name: ":security.services.role.default" label: Role Default securityServicesRoleRepositoryDefault: kind: panel.input.text id: securityServicesRoleRepositoryDefault - name: security.services.roleRepository.default + name: ":security.services.roleRepository.default" label: Role Repository Default emailServerSettings: @@ -578,7 +578,7 @@ emailServerSettings: smtpEnabled: kind: panel.input.checkbox id: smtpEnabled - name: smtp.enabled + name: ":smtp.enabled" label: "Enable SMTP?" hostname: kind: panel.input.text @@ -611,15 +611,15 @@ emailServerSettings: label: Properties smtpAuth: kind: panel.input.checkbox - name: mail.smtp.auth + name: ":mail.smtp.auth" label: SMTP Auth? smtpStartTls: kind: panel.input.checkbox - name: mail.smtp.starttls.enable + name: ":mail.smtp.starttls.enable" label: Start TLS? smtpSSLTrust: kind: panel.input.text - name: mail.smtp.ssl.trust + name: ":mail.smtp.ssl.trust" label: SSL Trust placeholder: localhost emailPrefix: @@ -641,9 +641,9 @@ notifications: helpContactInfo: kind: panel.input.email id: helpContactInfo - name: notifications.helpContactInfo + name: ":notifications.helpContactInfo" label: "Help Contact Info" - value: "!? XNAT.data.notifications['notifications.helpContactInfo'] || XNAT.data.siteConfig.adminEmail" +# value: "!? XNAT.data.notifications['notifications.helpContactInfo'] || XNAT.data.siteConfig.adminEmail" emailMessageSubhead: kind: panel.subhead @@ -652,19 +652,19 @@ notifications: emailMessageUserRegistration: kind: panel.textarea id: emailMessageUserRegistration - name: notifications.emailMessageUserRegistration + name: ":notifications.emailMessageUserRegistration" label: "User Registration" description: "Text of message emailed to users upon registration. Link for email validation is auto-populated." emailMessageForgotUsernameRequest: kind: panel.textarea id: emailMessageForgotUsernameRequest - name: notifications.emailMessageForgotUsernameRequest + name: ":notifications.emailMessageForgotUsernameRequest" label: "Forgot Username Request" description: "Text of message emailed to users upon lost username request." emailMessageForgotPasswordReset: kind: panel.textarea id: emailMessageForgotPasswordReset - name: notifications.emailMessageForgotPasswordReset + name: ":notifications.emailMessageForgotPasswordReset" label: "Password Reset" description: "Text of message emailed to users upon lost password reset. Link for password reset is auto-populated" @@ -708,28 +708,28 @@ notifications: emailRecipientErrorMessages: kind: panel.input.email id: emailRecipientErrorMessages - name: notifications.emailRecipientErrorMessages + name: ":notifications.emailRecipientErrorMessages" label: "Error Messages" description: "What email address(es) should receive error emails. Separate multiple email addresses with commas. If empty, emails will be sent to the site administrator email address." value: "!? XNAT.data.notifications['notifications.emailRecipientErrorMessages'] || XNAT.data.siteConfig.adminEmail" emailRecipientIssueReports: kind: panel.input.email id: emailRecipientIssueReports - name: notifications.emailRecipientIssueReports + name: ":notifications.emailRecipientIssueReports" label: "Issue Reports" description: "What email address(es) should receive issue reports. Separate multiple email addresses with commas. If empty, emails will be sent to the site administrator email address." value: "!? XNAT.data.notifications['notifications.emailRecipientIssueReports'] || XNAT.data.siteConfig.adminEmail" emailRecipientNewUserAlert: kind: panel.input.email id: emailRecipientNewUserAlert - name: notifications.emailRecipientNewUserAlert + name: ":notifications.emailRecipientNewUserAlert" label: "New User Alert" description: "What email address(es) should receive New User Registration emails. Separate multiple email addresses with commas. If empty, emails will be sent to the site administrator email address." value: "!? XNAT.data.notifications['notifications.emailRecipientNewUserAlert'] || XNAT.data.siteConfig.adminEmail" emailRecipientUpdate: kind: panel.input.email id: emailRecipientUpdate - name: notifications.emailRecipientUpdate + name: ":notifications.emailRecipientUpdate" label: "Updates" description: "What email address(es) should receive update emails. Separate multiple email addresses with commas. If empty, emails will be sent to the site administrator email address." value: "!? XNAT.data.notifications['notifications.emailRecipientUpdate'] || XNAT.data.siteConfig.adminEmail" @@ -788,7 +788,7 @@ themeManagement: uploadTheme: kind: panel.input.upload id: themeFileUpload - name: xnat.theme.upload + name: ":xnat.theme.upload" label: Upload a theme package description: Upload a zipped theme package for selection above. className: themeUploader @@ -806,17 +806,17 @@ authenticationMethods: xnatInternal: kind: panel.input.checkbox id: xnatInternal - name: provider.providers.xnatInternal + name: ":provider.providers.xnatInternal" label: XNAT (Internal) ldapProvider: kind: panel.input.checkbox id: ldapProvider - name: provider.providers.ldap + name: ":provider.providers.ldap" label: LDAP # oauthProvider: # kind: panel.input.checkbox # id: oauthProvider -# name: provider.providers.oauth +# name: ":provider.providers.oauth" # label: OAuth genericAuthenticationProvider: kind: panel.form @@ -921,7 +921,7 @@ registrationOptions: kind: panel.input.checkbox id: requireEmailVerificationToRegister name: emailVerification - label: "Require Email Verification To Register?" + label: "Require Email Verification <br>to Register?" description: > If true, users will receive an email after registering for an account and must click a link in the email to verify their email address before they are able to use their account. @@ -942,7 +942,7 @@ registrationOptions: kind: panel.input.checkbox id: autoEnableUserRegistration name: userRegistration - label: "Auto-enable User Registration?" + label: "Auto-enable <br>User Registration?" description: > If true, user accounts will be enabled automatically when the user registers. Users will be able to access the site and any 'public' projects immediately. If false, the site administrator will be required to manually enable user accounts. Either way the administrator @@ -951,7 +951,7 @@ registrationOptions: kind: panel.input.checkbox id: autoEnablePar name: par - label: "Auto-enable with Project Access Request?" + label: "Auto-enable with <br>Project Access Request?" description: > If true, user accounts created when accepting project access requests will always be enabled (and verified) automatically. If false, user accounts will only be enabled automatically if "Auto-enable User Registration?" is true. So this setting determines whether project @@ -960,8 +960,8 @@ registrationOptions: uiAllowNewUserComments: kind: panel.input.checkbox id: uiAllowNewUserComments - name: UI.allow-new-user-comments - label: "Allow User Comments on Registration?" + name: ":UI.allow-new-user-comments" + label: "Allow User Comments <br>on Registration?" manageDataTypes: kind: panel.form @@ -976,14 +976,45 @@ manageDataTypes: displayNameForGenericImageSessionSingular: kind: panel.input.text id: displayNameForGenericImageSessionSingular - name: displayNameForGenericImageSession.singular + name: ":displayNameForGenericImageSession.singular" label: "Singular Display Name For Generic Image Session Singular" displayNameForGenericImageSessionPlural: kind: panel.input.text id: displayNameForGenericImageSessionPlural - name: displayNameForGenericImageSession.plural + name: ":displayNameForGenericImageSession.plural" label: "Plural Display Name For Generic Image Session Singular" +sessionBuilder: + kind: panel.form + name: sessionBuilder + label: "Session Builder" + method: POST + contentType: json + action: /xapi/siteConfig/batch + load: XNAT.data.siteConfig + refresh: /xapi/siteConfig + contents: + sessionXmlRebuilderRepeat: + kind: panel.input.number + id: sessionXmlRebuilderRepeat + name: sessionXmlRebuilderRepeat + label: Session Idle Check Interval + placeholder: Interval in milliseconds + description: > + This controls how often the system checks to see if any incoming DICOM sessions in the prearchive have + been idle for longer than the configured session idle time. This value should be specified in + milliseconds and defaults to 60,000 ms or one minute. + sessionXmlRebuilderInterval: + kind: panel.input.number + id: sessionXmlRebuilderInterval + name: sessionXmlRebuilderInterval + label: Session Idle Time + placeholder: Time in minutes + description: > + This tells the system how long a DICOM session should sit idle—that is, with no new data added to the + session—before attempting to build a session document from the DICOM data. This value is specified in + minutes and defaults to 5 minutes. + anonymization: kind: panel.form name: Anonymization @@ -998,7 +1029,7 @@ anonymization: kind: panel.input.checkbox id: enableSitewideAnonymizationScript name: enableSitewideAnonymizationScript - label: "Enable Site-wide Anonymization Script" + label: "Enable Site-wide <br>Anonymization Script?" sitewideAnonymizationScript: kind: panel.textarea id: sitewideAnonymizationScript @@ -1021,7 +1052,7 @@ seriesImportFilter: kind: panel.input.checkbox id: enableSitewideSeriesImportFilter name: enableSitewideSeriesImportFilter - label: "Enable Site-wide Series Import Filter" + label: "Enable Site-wide <br>Series Import Filter?" sitewideSeriesImportFilterMode: kind: panel.select.single id: sitewideSeriesImportFilterMode @@ -1137,16 +1168,6 @@ sessionUploadMethod: description: > Details on how to configure an Upload Applet script may be found <a href="https://wiki.xnat.org/display/XKB/Adding+parameters+and+launch+requirements+for+the+upload+applet" target="_blank">here</a>. - sessionXmlRebuilderRepeat: - kind: panel.input.number - id: sessionXmlRebuilderRepeat - name: sessionXmlRebuilderRepeat - label: Session Xml Rebuilder Repeat - sessionXmlRebuilderInterval: - kind: panel.input.number - id: sessionXmlRebuilderInterval - name: sessionXmlRebuilderInterval - label: Session Xml Rebuilder Interval dicomScpReceivers: kind: panel @@ -1173,11 +1194,10 @@ dicomScpReceivers: contentType: json contents: scpId: - kind: panel.input.text + kind: panel.input.hidden id: scp-id - name: scpId - label: SCP ID - validation: required + name: id + className: hidden aeTitle: kind: panel.input.text id: scp-title @@ -1252,7 +1272,7 @@ dicomScpReceiversOld: defaultDicomReceiver: kind: panel.select.single id: defaultDicomReceiver - name: services.dicom.scp.aetitle + name: ":services.dicom.scp.aetitle" label: Default DICOM Receiver description: "AE Title for default DICOM receiver" receivedFileUser: @@ -1270,6 +1290,7 @@ fileSystem: contentType: json action: /xapi/siteConfig/batch load: XNAT.data.siteConfig + refresh: /xapi/siteConfig contents: ${archivePath} ${cachePath} @@ -1285,6 +1306,7 @@ misc: kind: panel.form name: misc label: Miscellaneous + footer: false method: POST contentType: json action: /xapi/siteConfig/batch @@ -1301,7 +1323,8 @@ misc: label: Development Utilities spawner: kind: panel.element - description: Manage spawner elements. + label: Spawner +# description: Manage spawner elements. contents: link: tag: a.link @@ -1311,14 +1334,15 @@ misc: html: Manage Spawner Elements swagger: kind: panel.element - description: View the Swagger page. + label: Swagger +# description: View the Swagger page. contents: link: tag: a.link element: href: ~/xapi/swagger-ui.html target: _blank - html: Swagger + html: View the Swagger page ################################################# @@ -1416,6 +1440,7 @@ adminPage: label: "Session Upload, Import & Anonymization" group: manageData contents: + ${sessionBuilder} ${anonymization} ${seriesImportFilter} ${petTracers} diff --git a/src/main/resources/org/nrg/xnat/messages/system.properties b/src/main/resources/org/nrg/xnat/messages/system.properties new file mode 100644 index 0000000000000000000000000000000000000000..75204fb33915fc407ba61545142049c9f77bd1c1 --- /dev/null +++ b/src/main/resources/org/nrg/xnat/messages/system.properties @@ -0,0 +1,10 @@ +apiInfo.title=XNAT REST API +apiInfo.description=The XNAT REST API (XAPI) functions provide access to XNAT internal functions for remote clients. +apiInfo.termsOfServiceUrl=http://www.xnat.org/download +apiInfo.contactName=XNAT +apiInfo.contactUrl=http://www.xnat.org +apiInfo.contactEmail=info@xnat.org +apiInfo.license=Simplified 2-Clause BSD +apiInfo.licenseUrl=https://opensource.org/licenses/BSD-2-Clause + +providerManager.providerNotFound=No authentication provider found for {0} \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/tags/page/head.tag b/src/main/webapp/WEB-INF/tags/page/head.tag index 67e051f33a773a6ea556ada94b2abe85d57eb0de..8d76685cc17adff2efdccca5366ef3ed8e332941 100755 --- a/src/main/webapp/WEB-INF/tags/page/head.tag +++ b/src/main/webapp/WEB-INF/tags/page/head.tag @@ -12,9 +12,9 @@ <title>${title}</title> - <c:if test="${empty hasInit}"> + <c:if test="${empty requestScope.hasInit}"> <pg:init> - <c:if test="${empty hasVars}"> + <c:if test="${empty requestScope.hasVars}"> <pg:jsvars/> </c:if> </pg:init> diff --git a/src/main/webapp/WEB-INF/tags/page/init.tag b/src/main/webapp/WEB-INF/tags/page/init.tag index fbcad80f7eadee9df6198646b911c2241b7eaa49..ffe47ff0335db6dc620a2884bbce64f82316cab9 100755 --- a/src/main/webapp/WEB-INF/tags/page/init.tag +++ b/src/main/webapp/WEB-INF/tags/page/init.tag @@ -3,66 +3,70 @@ <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> <%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %> -<%-- set empty user info --%> -<c:set var="loggedIn" value="false" scope="session"/> -<c:set var="username" value="-" scope="session"/> -<c:set var="isAdmin" value="false" scope="session"/> - -<%-- set vars for user --%> -<sec:authorize access="isAuthenticated()"> - <c:set var="loggedIn" value="true" scope="session"/> - <c:set var="username" value="${pageContext.request.userPrincipal.name}" scope="session"/> -</sec:authorize> - -<%-- REDIRECT IF NOT LOGGED IN (username will be '-') --%> -<c:if test="${username == '-'}"> - <%--<c:redirect url="/app/template/Login.vm"/>--%> -</c:if> +<c:if test="${empty requestScope.hasInit}"> -<sec:authorize access="hasAnyRole('Administrator', 'administrator', 'Admin', 'admin', 'ADMIN')"> - <c:set var="isAdmin" value="true" scope="session"/> -</sec:authorize> + <%-- set empty user info --%> + <c:set var="loggedIn" value="false" scope="session"/> + <c:set var="username" value="-" scope="session"/> + <c:set var="isAdmin" value="false" scope="session"/> -<c:set var="themeName" value="${cookie.THEME_NAME.value}" scope="session"/> + <%-- set vars for user --%> + <sec:authorize access="isAuthenticated()"> + <c:set var="loggedIn" value="true" scope="session"/> + <c:set var="username" value="${pageContext.request.userPrincipal.name}" scope="session"/> + </sec:authorize> -<%-- if there's a theme specified in the request, use that --%> -<c:if test="${empty themeName && not empty param.theme}"> - <c:set var="themeName" value="${param.theme}" scope="session"/> -</c:if> + <%-- REDIRECT IF NOT LOGGED IN (username will be '-') --%> + <c:if test="${username == '-'}"> + <%--<c:redirect url="/app/template/Login.vm"/>--%> + </c:if> -<%-- set 'siteRoot' to the root of your web app --%> -<c:set var="siteRoot" value="${pageContext.request.contextPath}" scope="session"/> + <sec:authorize access="hasAnyRole('Administrator', 'administrator', 'Admin', 'admin', 'ADMIN')"> + <c:set var="isAdmin" value="true" scope="session"/> + </sec:authorize> -<%-- add a leading slash if siteRoot is not empty and doesn't already start with a slash --%> -<c:if test="${siteRoot != '' && !fn:startsWith(siteRoot,'/')}"> - <c:set var="siteRoot" value="/${pageContext.request.contextPath}" scope="session"/> -</c:if> + <c:set var="themeName" value="${cookie.THEME_NAME.value}" scope="session"/> -<c:set var="themeRoot" value="${siteRoot}/themes/${themeName}" scope="session"/> -<c:set var="pageRoot" value="${themeRoot}/page" scope="session"/> + <%-- if there's a theme specified in the request, use that --%> + <c:if test="${empty themeName && not empty param.theme}"> + <c:set var="themeName" value="${param.theme}" scope="session"/> + </c:if> -<%-- if no themeName is found, set vars to use root items --%> -<c:if test="${empty themeName}"> - <c:set var="themeRoot" value="${siteRoot}" scope="session"/> - <c:set var="pageRoot" value="${siteRoot}/page" scope="session"/> -</c:if> + <%-- set 'siteRoot' to the root of your web app --%> + <c:set var="siteRoot" value="${pageContext.request.contextPath}" scope="session"/> -<%-- get session expiration time --%> -<c:set var="sessionExpiration" value="${cookie.SESSION_EXPIRATION_TIME.value}" scope="session"/> + <%-- add a leading slash if siteRoot is not empty and doesn't already start with a slash --%> + <c:if test="${siteRoot != '' && !fn:startsWith(siteRoot,'/')}"> + <c:set var="siteRoot" value="/${pageContext.request.contextPath}" scope="session"/> + </c:if> -<c:set var="csrfToken" value="0" scope="session"/> -<c:set var="landingPage" value="${pageRoot}/login/#!" scope="session"/> + <c:set var="themeRoot" value="${siteRoot}/themes/${themeName}" scope="session"/> + <c:set var="pageRoot" value="${themeRoot}/page" scope="session"/> -<c:if test="${loggedIn == true}"> - <c:set var="csrfToken" value="${sessionScope.XNAT_CSRF}" scope="session"/> - <c:set var="landingPage" value="${pageRoot}/home/#!" scope="session"/> -</c:if> + <%-- if no themeName is found, set vars to use root items --%> + <c:if test="${empty themeName}"> + <c:set var="themeRoot" value="${siteRoot}" scope="session"/> + <c:set var="pageRoot" value="${siteRoot}/page" scope="session"/> + </c:if> -<%-- is this page in a modal/dialog box --%> -<c:set var="isModal" value="${not empty param.modal && param.modal == 'true'}"/> -<c:set var="isDialog" value="${not empty param.dialog && param.dialog == 'true'}"/> + <%-- get session expiration time --%> + <c:set var="sessionExpiration" value="${cookie.SESSION_EXPIRATION_TIME.value}" scope="session"/> -<c:set var="hasInit" value="true" scope="request"/> + <c:set var="csrfToken" value="0" scope="session"/> + <c:set var="landingPage" value="${pageRoot}/login/#!" scope="session"/> + + <c:if test="${loggedIn == true}"> + <c:set var="csrfToken" value="${sessionScope.XNAT_CSRF}" scope="session"/> + <c:set var="landingPage" value="${pageRoot}/home/#!" scope="session"/> + </c:if> + + <%-- is this page in a modal/dialog box --%> + <c:set var="isModal" value="${not empty param.modal && param.modal == 'true'}"/> + <c:set var="isDialog" value="${not empty param.dialog && param.dialog == 'true'}"/> + + <c:set var="hasInit" value="true" scope="request"/> + +</c:if> <%-- inject content on init--%> <jsp:doBody/> diff --git a/src/main/webapp/WEB-INF/tags/page/jsvars.tag b/src/main/webapp/WEB-INF/tags/page/jsvars.tag index d6eca72c6c52bd26677561051893667f0119e339..23881f0d14eb2244daa652111c2eec8d327c95a7 100755 --- a/src/main/webapp/WEB-INF/tags/page/jsvars.tag +++ b/src/main/webapp/WEB-INF/tags/page/jsvars.tag @@ -24,4 +24,4 @@ console.log(PAGE); </script> -<c:set var="hasVars" value="true"/> +<c:set var="hasVars" value="true" scope="request"/> diff --git a/src/main/webapp/WEB-INF/tags/page/restricted.tag b/src/main/webapp/WEB-INF/tags/page/restricted.tag index dac0c39506843ab55776701d2b49337bd20aa8e5..4f04da09786335aa48b93af43d2a2fcc6d95e02c 100644 --- a/src/main/webapp/WEB-INF/tags/page/restricted.tag +++ b/src/main/webapp/WEB-INF/tags/page/restricted.tag @@ -6,16 +6,16 @@ <%-- restricts access to only admin users --%> -<c:if test="${empty hasInit}"> +<c:if test="${empty requestScope.hasInit}"> <pg:init> - <c:if test="${empty hasVars}"> + <c:if test="${empty requestScope.hasVars}"> <pg:jsvars/> </c:if> </pg:init> </c:if> <c:choose> - <c:when test="${isAdmin == true}"> + <c:when test="${sessionScope.isAdmin == true}"> <jsp:doBody/> diff --git a/src/main/webapp/WEB-INF/tags/page/xnat.tag b/src/main/webapp/WEB-INF/tags/page/xnat.tag index ec4046ad6e90330ce56982abf7f9e63cd674539c..88f301eb7a40504cd57f055e6e72c7ef5217611c 100644 --- a/src/main/webapp/WEB-INF/tags/page/xnat.tag +++ b/src/main/webapp/WEB-INF/tags/page/xnat.tag @@ -11,9 +11,9 @@ <head> - <c:if test="${empty hasInit}"> + <c:if test="${empty requestScope.hasInit}"> <pg:init> - <c:if test="${empty hasVars}"> + <c:if test="${empty requestScope.hasVars}"> <pg:jsvars/> </c:if> </pg:init> @@ -77,6 +77,7 @@ <script src="${SITE_ROOT}/scripts/lib/spawn/spawn.js"></script> <script src="${SITE_ROOT}/scripts/lib/js.cookie.js"></script> <script src="${SITE_ROOT}/scripts/lib/yamljs/dist/yaml.js"></script> + <script src="${SITE_ROOT}/scripts/lib/form2js/src/form2js.js"></script> <!-- XNAT utility functions --> <script src="${SITE_ROOT}/scripts/utils.js"></script> diff --git a/src/main/webapp/page/admin/content.jsp b/src/main/webapp/page/admin/content.jsp index 084adbec7d63612ab5af33437498baf6d1a10813..d6e095f4bdd6fad28aa6661fe2d2a85554e1bf08 100755 --- a/src/main/webapp/page/admin/content.jsp +++ b/src/main/webapp/page/admin/content.jsp @@ -45,6 +45,9 @@ <c:import url="/xapi/siteConfig" var="siteConfig"/> <c:import url="/xapi/notifications" var="notifications"/> + <script src="${sessionScope.siteRoot}/scripts/lib/ace/ace.js"></script> + <script src="${sessionScope.siteRoot}/scripts/xnat/app/codeEditor.js"></script> + <script> (function(){ diff --git a/src/main/webapp/page/admin/info/content.jsp b/src/main/webapp/page/admin/info/content.jsp index 3833bfde9b25d520291bfc365df73583e36060bd..b75a8b19775dc3ad5bfde5fd6ea35f2772158fa7 100644 --- a/src/main/webapp/page/admin/info/content.jsp +++ b/src/main/webapp/page/admin/info/content.jsp @@ -2,9 +2,9 @@ <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="pg" tagdir="/WEB-INF/tags/page" %> -<c:if test="${empty hasInit}"> +<c:if test="${empty requestScope.hasInit}"> <pg:init> - <c:if test="${empty hasVars}"> + <c:if test="${empty requestScope.hasVars}"> <pg:jsvars/> </c:if> </pg:init> diff --git a/src/main/webapp/page/admin/style.css b/src/main/webapp/page/admin/style.css index e122be50f46acea24744365aa8a0521b60c77e92..e6b79ad08118d5a5dfefa98cfc34cd4982bc262e 100644 --- a/src/main/webapp/page/admin/style.css +++ b/src/main/webapp/page/admin/style.css @@ -83,19 +83,39 @@ body.xnat .xmodal .panel { border: none; } .panel-title { font-weight: normal; font-size: 18px; line-height: inherit; } /* PANEL ELEMENTS */ -.panel .panel-element { clear: both; overflow: auto; margin: 0 0 30px;} +.panel .panel-element { margin: 15px 0; clear: both; overflow: visible; } +.panel .panel-element.stacked { margin: 0 0 30px;} .panel .panel-element:last-child { margin: 0; } .panel .panel-element > * { box-sizing: border-box; } .panel .panel-element .element-label { - color: #222; font-weight: bold; text-align: left; - line-height: 24px; vertical-align: middle; - display: block; + width: 30%; padding-right: 10px; float: left; + color: #222; font-weight: bold; text-align: right; +} +.panel .panel-element.stacked .element-label { + width: inherit; display: block; float: none; + text-align: left; } -.panel .panel-element .element-wrapper { display: block; } -.panel .panel-element .element-wrapper .description { margin: 3px 0; font-size: 12px; color: #848484;} +.panel .panel-element .element-wrapper { display: inline-block; width: 70%; float: right; } +.panel .panel-element.stacked .element-wrapper { display: block; width: inherit; float: none; } +.panel .panel-element .element-wrapper .description { margin: 3px 0 5px; color: #848484; } +.panel .panel-element.stacked .element-wrapper .description { font-size: 12px; } .panel .panel-element span.before { right: 10px; } .panel .panel-element span.after { margin-left: 10px; } .panel .panel-element label.small { font-weight: normal; } +.panel .panel-element input { + /* inputs shouldn't be wider than textareas */ + max-width: 100%; box-sizing: border-box; + margin-top: -6px; +} +.panel .panel-element input[type="checkbox"], +.panel .panel-element input[type="radio"] { + margin-top: 0; +} +/* */ +.panel .panel-element textarea { + width: 100%; margin-top: -6px; padding: 5px 7px; box-sizing: border-box; + font-family: Courier, monospace; font-weight: normal; +} .panel .input-bundle { margin-top: 5px; } @@ -106,14 +126,6 @@ body.xnat .xmodal .panel { border: none; } text-transform: uppercase; } -.panel .panel-element textarea { - width: 80%; padding: 5px 7px; - font-family: Courier, monospace; font-weight: normal; -} - -/* inputs shouldn't be wider than textareas */ -.panel .panel-element input { max-width: 80%; } - /* ELEMENT GROUP ITEMS */ .panel .panel-element-group .group-item .element-label { width: auto; } diff --git a/src/main/webapp/page/content.jsp b/src/main/webapp/page/content.jsp index e7deaccfcba687842739815e27ced60d4862dfe4..76a829faaaca43100d055fd5ebafd30fa11580e0 100755 --- a/src/main/webapp/page/content.jsp +++ b/src/main/webapp/page/content.jsp @@ -2,9 +2,9 @@ <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="pg" tagdir="/WEB-INF/tags/page" %> -<c:if test="${empty hasInit}"> +<c:if test="${empty requestScope.hasInit}"> <pg:init> - <c:if test="${empty hasVars}"> + <c:if test="${empty requestScope.hasVars}"> <pg:jsvars/> </c:if> </pg:init> @@ -22,7 +22,7 @@ var customPage = XNAT.app.customPage; var $pageContent = $('#page-content'); - customPage.getPage('', $pageContent); + customPage.getPage(['', '/#'], $pageContent); // window.onhashchange = function(){ // customPage.getPage('', $pageContent); diff --git a/src/main/webapp/scripts/globals.js b/src/main/webapp/scripts/globals.js index c92d56b97c230b3778b5080957f083ad6223d59a..0cb04e035a502eabb752768258ca84dfb560bc72 100644 --- a/src/main/webapp/scripts/globals.js +++ b/src/main/webapp/scripts/globals.js @@ -316,22 +316,47 @@ function setExtendedObject(obj, str, val){ // --> myVal == 'myXnatSiteId' function lookupObjectValue(root, objStr, prop){ - var val = '', delim = '.'; + var val = '', + delim = '.', + brackets = /[\]\[]/, + hasBrackets = false, + parts = []; if (!objStr) { - objStr = root; + objStr = root+''; root = window; } if (!objStr) return ''; root = root || window; - - // if 'objStr' contains colons, use those as the path delimeter - if (/:/.test(objStr)) { delim = ':' } - + + // if 'objStr' contains brackets, use bracket notation + if (objStr.indexOf('[') > -1) { + delim = brackets; + // remove leading and trailing brackets + //objStr = objStr.replace(/^\[|]$/g, '') + hasBrackets = true; + } + // if 'objStr' contains colons, use those as the path delimiter + else if (/:/.test(objStr)) { + delim = ':'; + } + // otherwise we're probably using dot notation + objStr.toString().trim().split(delim).forEach(function(part, i){ - part = part.trim(); + part = (part+'').trim(); + // if using brackets, trim quotes + if (hasBrackets) { + part = part.replace(/^[\['"]|['"\]]$/g, ''); + } + // only push non-empty parts + // this should filter items that + // start or end with a delimiter + if (part > '') parts.push(part); + }); + + parts.forEach(function(part, i){ // start at the root object if (i === 0) { val = root[part] || ''; diff --git a/src/main/webapp/scripts/imageScanData/scan_tools.js b/src/main/webapp/scripts/imageScanData/scan_tools.js index fbb20cd389bc90037ce9a0d092c3ae0d2c6806ec..381e93fe6dc5bfbf895e5db70a684cb5715d382c 100644 --- a/src/main/webapp/scripts/imageScanData/scan_tools.js +++ b/src/main/webapp/scripts/imageScanData/scan_tools.js @@ -349,7 +349,7 @@ function ScanEditor(_sessionID,_scanID,_options){ var sel = document.createElement("select"); sel.name = modality + "/quality"; sel.options[0] = new Option("(SELECT)", ""); - populateScanQualitySelector(serverRoot, this.options && this.options.project, sel, 1, this.scan.extension.Quality); + populateScanQualitySelector(serverRoot, window.projectScope, sel, 1, this.scan.extension.Quality); td2.appendChild(sel); tr.appendChild(td1); tr.appendChild(td2); @@ -1121,7 +1121,7 @@ function scanListingEditor(_tbody,_scanSet,_options){ if(scan.qual_input==undefined){ scan.qual_input=document.createElement("select"); scan.qual_input.options[0]=new Option("(SELECT)", ""); - populateScanQualitySelector(serverRoot, null, scan.qual_input, 1, scan.extension.Quality); + populateScanQualitySelector(serverRoot, window.projectScope, scan.qual_input, 1, scan.extension.Quality); } td.appendChild(scan.qual_input); tr.appendChild(td); diff --git a/src/main/webapp/scripts/lib/form2js/README.markdown b/src/main/webapp/scripts/lib/form2js/README.markdown new file mode 100755 index 0000000000000000000000000000000000000000..ad2b279d3cb74b0cc83412f1110b3db748285794 --- /dev/null +++ b/src/main/webapp/scripts/lib/form2js/README.markdown @@ -0,0 +1,230 @@ +form2js +------- + +Convenient way to collect **structured** form data into JavaScript object. +[Example](http://form2js.googlecode.com/hg/example/test.html). +Because everything is better with jQuery, jQuery plugin added, check out jquery.toObject.js. +If you have any questions/suggestions, find out something weird or illogical - feel free to post an issue. + +**Warning!** form2object.js and form2object function renamed to form2js.js and form2js respectively. +Old names are in v1.0 tag. + + +Details +======= + +This is **not** a serialization library. +Library used in example for JSON serialization is [http://www.json.org/js.html](http://www.json.org/js.html) +Structure of resulting object defined by _name_ attribute of form fields. +See examples below. +All this library does is collect form data and put it in a javascript object. +Obviously you can get a JSON/XML/etc string by serializing it, but that's not its only purpose. + + +Usage +===== + +``` javascript +form2js(rootNode, delimiter, skipEmpty, nodeCallback, useIdIfEmptyName) +``` + +Values of all inputs under the _rootNode_ will be collected into one object. +skipping empty inputs if _skipEmpty_ not false. + + +### Objects/nested objects + +Structure of resulting object defined in _name_ attributes of form fields (or _id_ if _name_ is empty and _useIdIfEmptyName_ parameter set to _true_). +_delimiter_ is "." (dot) by default, but can be changed. + +``` html +<input type="text" name="person.name.first" value="John" /> +<input type="text" name="person.name.last" value="Doe" /> +``` + +becomes + +``` json +{ + "person": { + "name": { + "first": "John", + "last": "Doe" + } + } +} +``` + + +### Arrays + +Several fields with the same name with brackets defines array of values. + +``` html +<label><input type="checkbox" name="person.favFood[]" value="steak" checked="checked" /> Steak</label> +<label><input type="checkbox" name="person.favFood[]" value="pizza"/> Pizza</label> +<label><input type="checkbox" name="person.favFood[]" value="chicken" checked="checked" /> Chicken</label> +``` + +becomes + +``` json +{ + "person": { + "favFood": [ "steak", "chicken" ] + } +} +``` + +### Arrays of objects/nested objects +Same index means same item in resulting array. +Index doesn't specify order (order of appearance in document will be used). + +``` html +<dl> + <dt>Give us your five friends' names and emails</dt> + <dd> + <label>Email <input type="text" name="person.friends[0].email" value="agent.smith@example.com" /></label> + <label>Name <input type="text" name="person.friends[0].name" value="Smith Agent"/></label> + </dd> + <dd> + <label>Email <input type="text" name="person.friends[1].email" value="n3o@example.com" /></label> + <label>Name <input type="text" name="person.friends[1].name" value="Thomas A. Anderson" /></label> + </dd> +</dl> +``` + +becomes + +``` json +{ + "person" : + { + "friends" : [ + { "email" : "agent.smith@example.com", "name" : "Smith Agent" }, + { "email" : "n3o@example.com", "name" : "Thomas A. Anderson" } + ] + } +} +``` + + +### Rails-style notation + +If array index starts with [a-zA-Z_], it will be treated as field of object. + +``` html +<dl> + <dt>Rails-style test</dt> + <dd> + <label>rails[field1][foo]<input type="text" name="rails[field1][foo]" value="baz" /></label> + <label>rails[field1][bar]<input type="text" name="rails[field1][bar]" value="qux" /></label> + </dd> + <dd> + <label>rails[field2][foo]<input type="text" name="rails[field2][foo]" value="baz" /></label> + <label>rails[field2][bar]<input type="text" name="rails[field2][bar]" value="qux" /></label> + </dd> +</dl> +``` + +will give us: + +``` json +{ + "rails": { + "field1": { + "foo": "baz", + "bar": "qux" + }, + "field2": { + "foo": "baz", + "bar": "qux" + } + } +} +``` + + +### Custom fields + +You can implement custom nodeCallback function (passed as 4th parameter to `form2object()`) to extract custom data: + +``` html +<dl id="dateTest"> +<dt>Date of birth:</dt> +<dd data-name="person.dateOfBirth" class="datefield"> + <select name="person.dateOfBirth.month"> + <option value="01">January</option> + <option value="02">February</option> + <option value="03">March</option> + <option value="04">April</option> + <option value="05">May</option> + <option value="06">June</option> + <option value="07">July</option> + <option value="08">August</option> + <option value="09">September</option> + <option value="10">October</option> + <option value="11">November</option> + <option value="12">December</option> + </select> + <input type="text" name="person.dateOfBirth.day" value="1" /> + <input type="text" name="person.dateOfBirth.year" value="2011" /> +</dd> +</dl> + +<script type="text/javascript"> + function processDate(node) + { + var dataName = node.getAttribute ? node.getAttribute('data-name') : '', + dayNode, + monthNode, + yearNode, + day, + year, + month; + + if (dataName && dataName != '' && node.className == 'datefield') + { + dayNode = node.querySelector('input[name="'+dataName + '.day"]'); + monthNode = node.querySelector('select[name="'+dataName + '.month"]'); + yearNode = node.querySelector('input[name="'+dataName + '.year"]'); + + day = dayNode.value; + year = yearNode.value; + month = monthNode.value; + + return { name: dataName, value: year + '-' + month + '-' + day}; + } + + return false; + } + + var formData = form2object('dateTest', '.', true, processDate); +</script> +``` + +using `processDate()` callback `formData` will contain + +``` json +{ + "person": { + "dateOfBirth": "2011-01-12" + } +} +``` + + +Why not `.serializeArray()`? +============================ + +JQuery's `.serializeArray()` works a bit different. +It makes this structure from markup in "Arrays of objects/nested objects" example: + +``` json +[ + { "person.friends[0].email" : "agent.smith@example.com" }, + { "person.friends[0].name" : "Smith Agent" }, + { "person.friends[1].email" : "n3o@example.com" }, + { "person.friends[1].name" : "Thomas A. Anderson" } +] +``` diff --git a/src/main/webapp/scripts/lib/form2js/example/customCallback.example.html b/src/main/webapp/scripts/lib/form2js/example/customCallback.example.html new file mode 100755 index 0000000000000000000000000000000000000000..1faf39d3b48ee3bad3bb612d7d9ce49cdc11b9e5 --- /dev/null +++ b/src/main/webapp/scripts/lib/form2js/example/customCallback.example.html @@ -0,0 +1,78 @@ +<!doctype html> + +<!--[if lt IE 7 ]> <html lang="ru" class="no-js ie6"> <![endif]--> +<!--[if IE 7 ]> <html lang="ru" class="no-js ie7"> <![endif]--> +<!--[if IE 8 ]> <html lang="ru" class="no-js ie8"> <![endif]--> +<!--[if IE 9 ]> <html lang="ru" class="no-js ie9"> <![endif]--> +<!--[if (gt IE 9)|!(IE)]><!--> +<html lang="ru" class="no-js"> <!--<![endif]--> + <head> + <meta charset="utf-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> + </head> + + <body> + <dl id="dateTest"> + <dt>Date of birth:</dt> + <dd data-name="person.dateOfBirth" class="datefield"> + <select name="person.dateOfBirth.month"> + <option value="01">January</option> + <option value="02">February</option> + <option value="03">March</option> + <option value="04">April</option> + <option value="05">May</option> + <option value="06">June</option> + <option value="07">July</option> + <option value="08">August</option> + <option value="09">September</option> + <option value="10">October</option> + <option value="11">November</option> + <option value="12">December</option> + </select> + <input type="text" name="person.dateOfBirth.day" value="1" /> + <input type="text" name="person.dateOfBirth.year" value="2011" /> + </dd> + </dl> + + <button onclick="getFormData()">Test</button> + + <pre><code id="testArea"> + </code></pre> + + <script type="text/javascript" src="../src/form2js.js"></script> + <script type="text/javascript" src="json2.js"></script> + <script type="text/javascript"> + function processDate(node) + { + var dataName = node.getAttribute ? node.getAttribute('data-name') : '', + dayNode, + monthNode, + yearNode, + day, + year, + month; + + if (dataName && dataName != '' && node.className == 'datefield') + { + dayNode = node.querySelector('input[name="'+dataName + '.day"]'); + monthNode = node.querySelector('select[name="'+dataName + '.month"]'); + yearNode = node.querySelector('input[name="'+dataName + '.year"]'); + + day = dayNode.value; + year = yearNode.value; + month = monthNode.value; + + return { name: dataName, value: year + '-' + month + '-' + day}; + } + + return false; + } + + function getFormData() + { + var formData = form2js('dateTest', '.', true, processDate); + document.getElementById('testArea').innerHTML = JSON.stringify(formData, null, '\t'); + } + </script> + </body> +</html> \ No newline at end of file diff --git a/src/main/webapp/scripts/lib/form2js/example/form2js.example.html b/src/main/webapp/scripts/lib/form2js/example/form2js.example.html new file mode 100755 index 0000000000000000000000000000000000000000..23a482acc94f72b023952af237baef3945abe5d1 --- /dev/null +++ b/src/main/webapp/scripts/lib/form2js/example/form2js.example.html @@ -0,0 +1,302 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html + PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head> + <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> + <title></title> +</head> + +<body> + +<form id="testForm" action="javascript:test()"> + <dl> + <dt><label for="person.nameFirstDisabled">Disabled field:</label></dt> + <dd><input id="person.nameFirstDisabled" disabled="disabled" type="text" name="person.name.firstDisabled" value="test" /></dd> + </dl> + + <dl> + <dt><label for="person.nameFirstEmptyName">Field with empty name:</label></dt> + <dd><input id="person.nameFirstEmptyName" type="text" name="" value="testEmptyName" /></dd> + </dl> + + <dl> + <dt><label for="person.nameFirstMissingName">Field with missing name:</label></dt> + <dd><input id="person.nameFirstMissingName" type="text" value="testMissingName" /></dd> + </dl> + + <dl> + <dt><label>Field with empty name missing id:</label></dt> + <dd><input type="text" name="" value="testEmptyNameMissingId" /></dd> + </dl> + + <dl> + <dt><label>Field with missing name missing id:</label></dt> + <dd><input type="text" value="testMissingNameMissingId" /></dd> + </dl> + + <dl> + <dt><label for="nameFirst">First name:</label></dt> + <dd><input id="nameFirst" type="text" name="person.name.first"/></dd> + </dl> + + <dl> + <dt><label for="nameLast">Last name:</label></dt> + <dd><input id="nameLast" type="text" name="person.name.last"/></dd> + </dl> + + <dl> + <dt>Gender:</dt> + <dd><label><input type="radio" name="person.gender" id="genderMale" value="male" /> Male</label></dd> + <dd><label><input type="radio" name="person.gender" id="genderFemale" value="female" /> Female</label></dd> + </dl> + + <dl> + <dt>City</dt> + <dd> + <select name="person.city"> + <optgroup label="Russia"> + <option value="msk">Moscow</option> + <option value="spb">St. Petersburg</option> + <option value="nsk">Novosibirsk</option> + <option value="ekb">Ekaterinburg</option> + </optgroup> + <optgroup label="Europe"> + <option value="london">London</option> + <option value="paris">Paris</option> + <option value="madrid">Madrid</option> + </optgroup> + </select> + </dd> + </dl> + + <dl> + <dt>Favorite food</dt> + <dd><label><input type="checkbox" name="person.favFood[]" value="steak"/> Steak</label></dd> + <dd><label><input type="checkbox" name="person.favFood[]" value="pizza"/> Pizza</label></dd> + <dd><label><input type="checkbox" name="person.favFood[]" value="chicken"/> Chicken</label></dd> + </dl> + + <dl> + <dt>Bool checkbox</dt> + <dd><label><input type="checkbox" name="person.agreeToKillAllHumanz" value="true"> I agree to kill all humanz</label></dd> + </dl> + + <dl> + <dt>Choose some colors</dt> + <dd> + <select name="person.colors" multiple="multiple"> + <optgroup label="Warm"> + <option value="green">Green</option> + <option value="orange">Orange</option> + <option value="red">Red</option> + </optgroup> + <optgroup label="Cold"> + <option value="blue">blue</option> + <option value="purple">purple</option> + </optgroup> + </select> + </dd> + </dl> + + <dl> + <dt>Multiple select (ZendForm syntax)</dt> + <dd> + <select name="person.colorsZend[]" multiple="multiple"> + <optgroup label="Warm"> + <option value="green">Green</option> + <option value="orange">Orange</option> + <option value="red">Red</option> + </optgroup> + <optgroup label="Cold"> + <option value="blue">blue</option> + <option value="purple">purple</option> + </optgroup> + </select> + </dd> + </dl> + + <dl> + <dt>Give us your five friends' names and emails</dt> + <dd> + <label>Email0 <input type="text" name="person.friends[0].emails[0].email" value="test1" /></label> + <label>Email1 <input type="text" name="person.friends[0].emails[1].email" value="test2" /></label> + <label>Name <input type="text" name="person.friends[0].name"/></label> + </dd> + <dd> + <label>Email0 <input type="text" name="person.friends[1].emails[0].email" value="test3" /></label> + <label>Email1 <input type="text" name="person.friends[1].emails[1].email" value="test4" /></label> + <label>Name <input type="text" name="person.friends[1].name"/></label> + </dd> + <dd> + <label>Email0 <input type="text" name="person.friends[2].emails[0].email" value="test5" /></label> + <label>Email1 <input type="text" name="person.friends[2].emails[1].email" value="test6" /></label> + <label>Name <input type="text" name="person.friends[2].name"/></label> + </dd> + <dd> + <label>Email0 <input type="text" name="person.friends[3].emails[0].email" value="test7" /></label> + <label>Email1 <input type="text" name="person.friends[3].emails[1].email" value="test8" /></label> + <label>Name <input type="text" name="person.friends[3].name"/></label> + </dd> + <dd> + <label>Email0 <input type="text" name="person.friends[4].emails[0].email" value="test9" /></label> + <label>Email1 <input type="text" name="person.friends[4].emails[1].email" value="test0" /></label> + <label>Name <input type="text" name="person.friends[4].name"/></label> + </dd> + </dl> + + <dl> + <dt>Multiple select inside an array (ZendForm syntax)</dt> + <dd> + <select name="person.friends[4].colorsZend[]" multiple="multiple"> + <optgroup label="Warm"> + <option value="green">Green</option> + <option value="orange">Orange</option> + <option value="red">Red</option> + </optgroup> + <optgroup label="Cold"> + <option value="blue">blue</option> + <option value="purple">purple</option> + </optgroup> + </select> + </dd> + </dl> + + <dl> + <dt>Array of arrays test</dt> + <dd> + <label>person.array[0][0]<input type="text" name="person.array[0][0]" value="test0-0" /></label> + <label>person.array[0][1]<input type="text" name="person.array[0][1]" value="test0-1" /></label> + <label>person.array[0][2]<input type="text" name="person.array[0][2]" value="test0-2" /></label> + </dd> + <dd> + <label>person.array[1][0]<input type="text" name="person.array[1][0]" value="test1-0" /></label> + <label>person.array[1][1]<input type="text" name="person.array[1][1]" value="test1-1" /></label> + <label>person.array[1][2]<input type="text" name="person.array[1][2]" value="test1-2" /></label> + </dd> + </dl> + + <dl> + <dt>Ruby-style test</dt> + <dd> + <label>person.ruby[field_1][foo]<input type="text" name="person.ruby[field_1][foo_baz]" value="baz" /></label> + <label>person.ruby[field_1][bar]<input type="text" name="person.ruby[field_1][bar_baz]" value="qux" /></label> + </dd> + <dd> + <label>person.ruby[field2][foo]<input type="text" name="person.ruby[field2][foo]" value="baz" /></label> + <label>person.ruby[field2][bar]<input type="text" name="person.ruby[field2][bar]" value="qux" /></label> + </dd> + </dl> + + <dl> + <dt>Fieldset test</dt> + <dd> + <fieldset><legend>Fieldset</legend> + <input type="text" name="person.fieldset.foo" value="baz" /> + <input type="text" name="person.fieldset.bar" value="qux" /> + </fieldset> + </dd> + </dl> + + <dl> + <dt>Textarea test</dt> + <dd> + <textarea rows="5" cols="20" name="person.bio">Some bio here, just for test.</textarea> + </dd> + </dl> + + <dl> + <dt>Tables test</dt> + <dd> + <table> + <tr> + <td> + <select name="table.sides[0].player" id="p1" style="width:100px"> + <option value="spartacus">Spartacus</option> + <option value="dinamo">Dinamo</option> + </select> + </td> + <td align="center"> + vs + </td> + <td> + <select name="table.sides[1].player" id="p2" style="width:100px"> + <option value="spartacus">Spartacus</option> + <option value="dinamo">Dinamo</option> + </select> + </td> + </tr> + <tr> + <td> + <select name="table.sides[0].score" id="s1" style="width:100px"> + <option value="0">0</option> + <option value="1">1</option> + <option value="2">2</option> + <option value="3">3</option> + <option value="4">4</option> + <option value="5">5</option> + <option value="6">6</option> + <option value="7">7</option> + <option value="8">8</option> + <option value="9">9</option> + </select> + </td> + <td align="center"> + - + </td> + <td> + <select name="table.sides[1].score" id="s2" style="width:100px"> + <option value="0">0</option> + <option value="1">1</option> + <option value="2">2</option> + <option value="3">3</option> + <option value="4">4</option> + <option value="5">5</option> + <option value="6">6</option> + <option value="7">7</option> + <option value="8">8</option> + <option value="9">9</option> + </select> + </td> + </tr> + </table> + + </dd> + </dl> + + <dl> + <dt>Custom callback test:</dt> + <dd> + <div id="person.callbackTest">test test test</div> + </dd> + </dl> + + <dl> + <dt></dt> + <dd><input type="submit" /></dd> + </dl> +</form> + +<pre><code id="testArea"> +</code></pre> + +<script type="text/javascript" src="../src/form2js.js"></script> +<script type="text/javascript" src="json2.js"></script> +<script type="text/javascript"> + function test() + { + var formData = form2js('testForm', '.', true, + function(node) + { + if (node.id && node.id.match(/callbackTest/)) + { + return { name: node.id, value: node.innerHTML }; + } + }); + + document.getElementById('testArea').innerHTML = JSON.stringify(formData, null, '\t'); + } +</script> +</body> +</html> \ No newline at end of file diff --git a/src/main/webapp/scripts/lib/form2js/example/jquery.example.html b/src/main/webapp/scripts/lib/form2js/example/jquery.example.html new file mode 100755 index 0000000000000000000000000000000000000000..c59d66b2829c7044ab6f0bb912a47da91d353560 --- /dev/null +++ b/src/main/webapp/scripts/lib/form2js/example/jquery.example.html @@ -0,0 +1,237 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html + PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head> + <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> + <title></title> +</head> + +<body> + +<form id="testForm"> + <dl> + <dt><label for="nameFirst">First name:</label></dt> + <dd><input id="nameFirst" type="text" name="person.name.first"/></dd> + </dl> + + <dl> + <dt><label for="nameLast">Last name:</label></dt> + <dd><input id="nameLast" type="text" name="person.name.last"/></dd> + </dl> + + <dl> + <dt>Gender:</dt> + <dd><label><input type="radio" name="person.gender" id="genderMale" value="male" /> Male</label></dd> + <dd><label><input type="radio" name="person.gender" id="genderFemale" value="female" /> Female</label></dd> + </dl> + + <dl> + <dt>City</dt> + <dd> + <select name="person.city"> + <optgroup label="Russia"> + <option value="msk">Moscow</option> + <option value="spb">St. Petersburg</option> + <option value="nsk">Novosibirsk</option> + <option value="ekb">Ekaterinburg</option> + </optgroup> + <optgroup label="Europe"> + <option value="london">London</option> + <option value="paris">Paris</option> + <option value="madrid">Madrid</option> + </optgroup> + </select> + </dd> + </dl> + + <dl> + <dt>Favorite food</dt> + <dd><label><input type="checkbox" name="person.favFood[]" value="steak"/> Steak</label></dd> + <dd><label><input type="checkbox" name="person.favFood[]" value="pizza"/> Pizza</label></dd> + <dd><label><input type="checkbox" name="person.favFood[]" value="chicken"/> Chicken</label></dd> + </dl> + + <dl> + <dt>Choose some colors</dt> + <dd> + <select name="person.colors" multiple="multiple"> + <optgroup label="Warm"> + <option value="green">Green</option> + <option value="orange">Orange</option> + <option value="red">Red</option> + </optgroup> + <optgroup label="Cold"> + <option value="blue">blue</option> + <option value="purple">purple</option> + </optgroup> + </select> + </dd> + </dl> + + <dl> + <dt>Give us your five friends' names and emails</dt> + <dd> + <label>Email0 <input type="text" name="person.friends[0].emails[0].email" value="test1" /></label> + <label>Email1 <input type="text" name="person.friends[0].emails[1].email" value="test2" /></label> + <label>Name <input type="text" name="person.friends[0].name"/></label> + </dd> + <dd> + <label>Email0 <input type="text" name="person.friends[1].emails[0].email" value="test3" /></label> + <label>Email1 <input type="text" name="person.friends[1].emails[1].email" value="test4" /></label> + <label>Name <input type="text" name="person.friends[1].name"/></label> + </dd> + <dd> + <label>Email0 <input type="text" name="person.friends[2].emails[0].email" value="test5" /></label> + <label>Email1 <input type="text" name="person.friends[2].emails[1].email" value="test6" /></label> + <label>Name <input type="text" name="person.friends[2].name"/></label> + </dd> + <dd> + <label>Email0 <input type="text" name="person.friends[3].emails[0].email" value="test7" /></label> + <label>Email1 <input type="text" name="person.friends[3].emails[1].email" value="test8" /></label> + <label>Name <input type="text" name="person.friends[3].name"/></label> + </dd> + <dd> + <label>Email0 <input type="text" name="person.friends[4].emails[0].email" value="test9" /></label> + <label>Email1 <input type="text" name="person.friends[4].emails[1].email" value="test0" /></label> + <label>Name <input type="text" name="person.friends[4].name"/></label> + </dd> + </dl> + + <dl> + <dt>Fieldset test</dt> + <dd> + <fieldset><legend>Fieldset</legend> + <input type="text" name="person.fieldset.foo" value="baz" /> + <input type="text" name="person.fieldset.bar" value="qux" /> + </fieldset> + </dd> + </dl> + + <dl> + <dt>Textarea test</dt> + <dd> + <textarea rows="5" cols="20" name="person.bio">Some bio here, just for test.</textarea> + </dd> + </dl> + + <dl> + <dt>Tables test</dt> + <dd> + <table> + <tr> + <td> + <select name="table.sides[0].player" id="p1" style="width:100px"> + <option value="spartacus">Spartacus</option> + <option value="dinamo">Dinamo</option> + </select> + </td> + <td align="center"> + vs + </td> + <td> + <select name="table.sides[1].player" id="p2" style="width:100px"> + <option value="spartacus">Spartacus</option> + <option value="dinamo">Dinamo</option> + </select> + </td> + </tr> + <tr> + <td> + <select name="table.sides[0].score" id="s1" style="width:100px"> + <option value="0">0</option> + <option value="1">1</option> + <option value="2">2</option> + <option value="3">3</option> + <option value="4">4</option> + <option value="5">5</option> + <option value="6">6</option> + <option value="7">7</option> + <option value="8">8</option> + <option value="9">9</option> + </select> + </td> + <td align="center"> + - + </td> + <td> + <select name="table.sides[1].score" id="s2" style="width:100px"> + <option value="0">0</option> + <option value="1">1</option> + <option value="2">2</option> + <option value="3">3</option> + <option value="4">4</option> + <option value="5">5</option> + <option value="6">6</option> + <option value="7">7</option> + <option value="8">8</option> + <option value="9">9</option> + </select> + </td> + </tr> + </table> + + </dd> + </dl> + + <dl> + <dt>Custom callback test:</dt> + <dd> + <div id="person.callbackTest">test test test</div> + </dd> + </dl> + + <dl> + <dt></dt> + <dd><input type="submit" /></dd> + </dl> +</form> + +<dl> + <dt><label for="selector">Form selector</label></dt> + <dd><input id="selector" name="selector" value="#testForm"></dd> + <dd><p>Try '#testForm dl'</p></dd> +</dl> + + +<h2>mode: first</h2> +<pre><code id="testAreaFirst"> +</code></pre> + +<h2>mode: all</h2> +<pre><code id="testAreaAll"> +</code></pre> + +<h2>mode: combine</h2> +<pre><code id="testAreaCombine"> +</code></pre> + +<script type="text/javascript" src="http://code.jquery.com/jquery-1.6.1.min.js"></script> +<script type="text/javascript" src="../src/form2js.js"></script> +<script type="text/javascript" src="../src/jquery.toObject.js"></script> +<script type="text/javascript" src="json2.js"></script> +<script type="text/javascript"> + (function($){ + + function test(evt){ + evt.preventDefault(); + + var selector = $('#selector').val(), + formDataFirst = $(selector).toObject({mode: 'first'}), + formDataAll = $(selector).toObject({mode: 'all'}), + formDataCombine = $(selector).toObject({mode: 'combine'}); + + $('#testAreaFirst').html(JSON.stringify(formDataFirst, null, '\t')); + $('#testAreaAll').html(JSON.stringify(formDataAll, null, '\t')); + $('#testAreaCombine').html(JSON.stringify(formDataCombine, null, '\t')); + } + + $(function(){ + $('input[type=submit]').click(test); + }); + + })(jQuery); +</script> +</body> +</html> \ No newline at end of file diff --git a/src/main/webapp/scripts/lib/form2js/example/js2form.example.html b/src/main/webapp/scripts/lib/form2js/example/js2form.example.html new file mode 100755 index 0000000000000000000000000000000000000000..27e03b81c2643bff090e939284ced79be8ee4beb --- /dev/null +++ b/src/main/webapp/scripts/lib/form2js/example/js2form.example.html @@ -0,0 +1,138 @@ +<!doctype html> + +<!--[if lt IE 7 ]> <html lang="ru" class="no-js ie6"> <![endif]--> +<!--[if IE 7 ]> <html lang="ru" class="no-js ie7"> <![endif]--> +<!--[if IE 8 ]> <html lang="ru" class="no-js ie8"> <![endif]--> +<!--[if IE 9 ]> <html lang="ru" class="no-js ie9"> <![endif]--> +<!--[if (gt IE 9)|!(IE)]><!--> +<html lang="ru" class="no-js"> <!--<![endif]--> + <head> + <meta charset="utf-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> + </head> + + <body> + <form id="testForm"> + <dl> + <dt><label>foo.name.first</label></dt> + <dd><input type="text" name="foo.name.first" value=""></dd> + </dl> + <dl> + <dt><label>foo.name.last</label></dt> + <dd><input type="text" name="foo.name.last" value=""></dd> + </dl> + <dl> + <dt><label>bar.name.first</label></dt> + <dd><input type="text" name="bar.name.first" value=""></dd> + </dl> + <dl> + <dt><label>bar.name.last</label></dt> + <dd><input type="text" name="bar.name.last" value=""></dd> + </dl> + <dl> + <dt><label>bar.Emails[]</label></dt> + <dd><input type="text" name="bar.Emails[]" value=""></dd> + <dd><input type="text" name="bar.Emails[]" value=""></dd> + <dd><input type="text" name="bar.Emails[]" value=""></dd> + <dd><input type="text" name="bar.Emails[]" value=""></dd> + <dd><input type="text" name="bar.Emails[]" value=""></dd> + <dd><input type="text" name="bar.Emails[]" value=""></dd> + </dl> + <dl> + <dt><label>bar.Addresses[5]</label></dt> + <dd><input type="text" name="bar.Addresses[5].Zip" value=""></dd> + <dd><input type="text" name="bar.Addresses[5].City" value=""></dd> + <dd><input type="text" name="bar.Addresses[5].Street" value=""></dd> + </dl> + <dl> + <dt><label>bar.Addresses[1]</label></dt> + <dd><input type="text" name="bar.Addresses[1].Zip" value=""></dd> + <dd><input type="text" name="bar.Addresses[1].City" value=""></dd> + <dd><input type="text" name="bar.Addresses[1].Street" value=""></dd> + </dl> + <dl> + <dt><label>foo.selectArray[]</label></dt> + <dd> + <select name="foo.selectArray[]" multiple> + <option value="green">Green</option> + <option value="red">Red</option> + <option value="blue">Blue</option> + <option value="yellow">Yellow</option> + </select> + </dd> + </dl> + <dl> + <dt><label>foo.checkboxArray[]</label></dt> + <dd> + <label><input type="checkbox" name="foo.checkboxArray[]" value="steak"> Steak</label> + <label><input type="checkbox" name="foo.checkboxArray[]" value="pizza"> Pizza</label> + <label><input type="checkbox" name="foo.checkboxArray[]" value="chicken"> Chicken</label> + <label><input type="checkbox" name="foo.checkboxArray[]" value="pepperoni"> Pepperoni</label> + </dd> + </dl> + <dl> + <dt><label>foo.radio</label></dt> + <dd> + <label><input type="radio" name="foo.radio" value="1"> 1</label> + <label><input type="radio" name="foo.radio" value="2"> 2</label> + <label><input type="radio" name="foo.radio" value="3"> 3</label> + <label><input type="radio" name="foo.radio" value="4"> 4</label> + </dd> + </dl> + <button type="button" onclick="getJson()">Get JSON</button> + <button type="reset">Reset form</button> + </form> + + <div> + <textarea id="src" cols="70" rows="20"> +{ + "foo":{ + "radio":"3", + "name":{ + "first":"Foo-First", + "last":"Foo-Last" + }, + "selectArray":[ "green", "yellow" ], + "checkboxArray":[ "steak", "chicken" ] + }, + "bar":{ + "name":{ + "first":"Bar-First", + "last":"Bar-Last" + }, + "Emails":[ + "mail1@example.com", + "mail2@example.com", + "mail3@example.com", + "mail4@example.com", + "mail5@example.com", + "mail6@example.com" + ], + "Addresses":[ + {"Zip":"12345","City":"NY","Street":"13, Boardwalk"}, + {"Zip":"98765","City":"LA","Street":"Under the bridge"} + ] + } +} + </textarea> + </div> + <button type="button" onclick="populateForm()">Populate form</button> + + <script type="text/javascript" src="../src/form2js.js"></script> + <script type="text/javascript" src="../src/js2form.js"></script> + <script type="text/javascript" src="json2.js"></script> + <script type="text/javascript"> + function getJson() + { + document.getElementById('src').value = JSON.stringify(form2js(document.getElementById('testForm'))); + } + + function populateForm() + { + var data = document.getElementById('src').value; + data = JSON.parse(data); + js2form(document.getElementById('testForm'), data); + } + </script> + </body> +</html> \ No newline at end of file diff --git a/src/main/webapp/scripts/lib/form2js/example/json2.js b/src/main/webapp/scripts/lib/form2js/example/json2.js new file mode 100755 index 0000000000000000000000000000000000000000..6bdea10e51ff2a00a271af3a1492fa7436f7463e --- /dev/null +++ b/src/main/webapp/scripts/lib/form2js/example/json2.js @@ -0,0 +1,482 @@ +/* + http://www.JSON.org/json2.js + 2010-08-25 + + Public Domain. + + NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + + See http://www.JSON.org/js.html + + + This code should be minified before deployment. + See http://javascript.crockford.com/jsmin.html + + USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO + NOT CONTROL. + + + This file creates a global JSON object containing two methods: stringify + and parse. + + JSON.stringify(value, replacer, space) + value any JavaScript value, usually an object or array. + + replacer an optional parameter that determines how object + values are stringified for objects. It can be a + function or an array of strings. + + space an optional parameter that specifies the indentation + of nested structures. If it is omitted, the text will + be packed without extra whitespace. If it is a number, + it will specify the number of spaces to indent at each + level. If it is a string (such as '\t' or ' '), + it contains the characters used to indent at each level. + + This method produces a JSON text from a JavaScript value. + + When an object value is found, if the object contains a toJSON + method, its toJSON method will be called and the result will be + stringified. A toJSON method does not serialize: it returns the + value represented by the name/value pair that should be serialized, + or undefined if nothing should be serialized. The toJSON method + will be passed the key associated with the value, and this will be + bound to the value + + For example, this would serialize Dates as ISO strings. + + Date.prototype.toJSON = function (key) { + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + return this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z'; + }; + + You can provide an optional replacer method. It will be passed the + key and value of each member, with this bound to the containing + object. The value that is returned from your method will be + serialized. If your method returns undefined, then the member will + be excluded from the serialization. + + If the replacer parameter is an array of strings, then it will be + used to select the members to be serialized. It filters the results + such that only members with keys listed in the replacer array are + stringified. + + Values that do not have JSON representations, such as undefined or + functions, will not be serialized. Such values in objects will be + dropped; in arrays they will be replaced with null. You can use + a replacer function to replace those with JSON values. + JSON.stringify(undefined) returns undefined. + + The optional space parameter produces a stringification of the + value that is filled with line breaks and indentation to make it + easier to read. + + If the space parameter is a non-empty string, then that string will + be used for indentation. If the space parameter is a number, then + the indentation will be that many spaces. + + Example: + + text = JSON.stringify(['e', {pluribus: 'unum'}]); + // text is '["e",{"pluribus":"unum"}]' + + + text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); + // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' + + text = JSON.stringify([new Date()], function (key, value) { + return this[key] instanceof Date ? + 'Date(' + this[key] + ')' : value; + }); + // text is '["Date(---current time---)"]' + + + JSON.parse(text, reviver) + This method parses a JSON text to produce an object or array. + It can throw a SyntaxError exception. + + The optional reviver parameter is a function that can filter and + transform the results. It receives each of the keys and values, + and its return value is used instead of the original value. + If it returns what it received, then the structure is not modified. + If it returns undefined then the member is deleted. + + Example: + + // Parse the text. Values that look like ISO date strings will + // be converted to Date objects. + + myData = JSON.parse(text, function (key, value) { + var a; + if (typeof value === 'string') { + a = +/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); + if (a) { + return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], + +a[5], +a[6])); + } + } + return value; + }); + + myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { + var d; + if (typeof value === 'string' && + value.slice(0, 5) === 'Date(' && + value.slice(-1) === ')') { + d = new Date(value.slice(5, -1)); + if (d) { + return d; + } + } + return value; + }); + + + This is a reference implementation. You are free to copy, modify, or + redistribute. +*/ + +/*jslint evil: true, strict: false */ + +/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, + call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, + getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, + lastIndex, length, parse, prototype, push, replace, slice, stringify, + test, toJSON, toString, valueOf +*/ + + +// Create a JSON object only if one does not already exist. We create the +// methods in a closure to avoid creating global variables. + +if (!this.JSON) { + this.JSON = {}; +} + +(function () { + + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + if (typeof Date.prototype.toJSON !== 'function') { + + Date.prototype.toJSON = function (key) { + + return isFinite(this.valueOf()) ? + this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z' : null; + }; + + String.prototype.toJSON = + Number.prototype.toJSON = + Boolean.prototype.toJSON = function (key) { + return this.valueOf(); + }; + } + + var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + gap, + indent, + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }, + rep; + + + function quote(string) { + +// If the string contains no control characters, no quote characters, and no +// backslash characters, then we can safely slap some quotes around it. +// Otherwise we must also replace the offending characters with safe escape +// sequences. + + escapable.lastIndex = 0; + return escapable.test(string) ? + '"' + string.replace(escapable, function (a) { + var c = meta[a]; + return typeof c === 'string' ? c : + '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' : + '"' + string + '"'; + } + + + function str(key, holder) { + +// Produce a string from holder[key]. + + var i, // The loop counter. + k, // The member key. + v, // The member value. + length, + mind = gap, + partial, + value = holder[key]; + +// If the value has a toJSON method, call it to obtain a replacement value. + + if (value && typeof value === 'object' && + typeof value.toJSON === 'function') { + value = value.toJSON(key); + } + +// If we were called with a replacer function, then call the replacer to +// obtain a replacement value. + + if (typeof rep === 'function') { + value = rep.call(holder, key, value); + } + +// What happens next depends on the value's type. + + switch (typeof value) { + case 'string': + return quote(value); + + case 'number': + +// JSON numbers must be finite. Encode non-finite numbers as null. + + return isFinite(value) ? String(value) : 'null'; + + case 'boolean': + case 'null': + +// If the value is a boolean or null, convert it to a string. Note: +// typeof null does not produce 'null'. The case is included here in +// the remote chance that this gets fixed someday. + + return String(value); + +// If the type is 'object', we might be dealing with an object or an array or +// null. + + case 'object': + +// Due to a specification blunder in ECMAScript, typeof null is 'object', +// so watch out for that case. + + if (!value) { + return 'null'; + } + +// Make an array to hold the partial results of stringifying this object value. + + gap += indent; + partial = []; + +// Is the value an array? + + if (Object.prototype.toString.apply(value) === '[object Array]') { + +// The value is an array. Stringify every element. Use null as a placeholder +// for non-JSON values. + + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || 'null'; + } + +// Join all of the elements together, separated with commas, and wrap them in +// brackets. + + v = partial.length === 0 ? '[]' : + gap ? '[\n' + gap + + partial.join(',\n' + gap) + '\n' + + mind + ']' : + '[' + partial.join(',') + ']'; + gap = mind; + return v; + } + +// If the replacer is an array, use it to select the members to be stringified. + + if (rep && typeof rep === 'object') { + length = rep.length; + for (i = 0; i < length; i += 1) { + k = rep[i]; + if (typeof k === 'string') { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } else { + +// Otherwise, iterate through all of the keys in the object. + + for (k in value) { + if (Object.hasOwnProperty.call(value, k)) { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } + +// Join all of the member texts together, separated with commas, +// and wrap them in braces. + + v = partial.length === 0 ? '{}' : + gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + + mind + '}' : '{' + partial.join(',') + '}'; + gap = mind; + return v; + } + } + +// If the JSON object does not yet have a stringify method, give it one. + + if (typeof JSON.stringify !== 'function') { + JSON.stringify = function (value, replacer, space) { + +// The stringify method takes a value and an optional replacer, and an optional +// space parameter, and returns a JSON text. The replacer can be a function +// that can replace values, or an array of strings that will select the keys. +// A default replacer method can be provided. Use of the space parameter can +// produce text that is more easily readable. + + var i; + gap = ''; + indent = ''; + +// If the space parameter is a number, make an indent string containing that +// many spaces. + + if (typeof space === 'number') { + for (i = 0; i < space; i += 1) { + indent += ' '; + } + +// If the space parameter is a string, it will be used as the indent string. + + } else if (typeof space === 'string') { + indent = space; + } + +// If there is a replacer, it must be a function or an array. +// Otherwise, throw an error. + + rep = replacer; + if (replacer && typeof replacer !== 'function' && + (typeof replacer !== 'object' || + typeof replacer.length !== 'number')) { + throw new Error('JSON.stringify'); + } + +// Make a fake root object containing our value under the key of ''. +// Return the result of stringifying the value. + + return str('', {'': value}); + }; + } + + +// If the JSON object does not yet have a parse method, give it one. + + if (typeof JSON.parse !== 'function') { + JSON.parse = function (text, reviver) { + +// The parse method takes a text and an optional reviver function, and returns +// a JavaScript value if the text is a valid JSON text. + + var j; + + function walk(holder, key) { + +// The walk method is used to recursively walk the resulting structure so +// that modifications can be made. + + var k, v, value = holder[key]; + if (value && typeof value === 'object') { + for (k in value) { + if (Object.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; + } + } + } + } + return reviver.call(holder, key, value); + } + + +// Parsing happens in four stages. In the first stage, we replace certain +// Unicode characters with escape sequences. JavaScript handles many characters +// incorrectly, either silently deleting them, or treating them as line endings. + + text = String(text); + cx.lastIndex = 0; + if (cx.test(text)) { + text = text.replace(cx, function (a) { + return '\\u' + + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } + +// In the second stage, we run the text against regular expressions that look +// for non-JSON patterns. We are especially concerned with '()' and 'new' +// because they can cause invocation, and '=' because it can cause mutation. +// But just to be safe, we want to reject all unexpected forms. + +// We split the second stage into 4 regexp operations in order to work around +// crippling inefficiencies in IE's and Safari's regexp engines. First we +// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we +// replace all simple value tokens with ']' characters. Third, we delete all +// open brackets that follow a colon or comma or that begin the text. Finally, +// we look to see that the remaining characters are only whitespace or ']' or +// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. + + if (/^[\],:{}\s]*$/ +.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') +.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') +.replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { + +// In the third stage we use the eval function to compile the text into a +// JavaScript structure. The '{' operator is subject to a syntactic ambiguity +// in JavaScript: it can begin a block or an object literal. We wrap the text +// in parens to eliminate the ambiguity. + + j = eval('(' + text + ')'); + +// In the optional fourth stage, we recursively walk the new structure, passing +// each name/value pair to a reviver function for possible transformation. + + return typeof reviver === 'function' ? + walk({'': j}, '') : j; + } + +// If the text is not JSON parseable, then a SyntaxError is thrown. + + throw new SyntaxError('JSON.parse'); + }; + } +}()); diff --git a/src/main/webapp/scripts/lib/form2js/index.js b/src/main/webapp/scripts/lib/form2js/index.js new file mode 100755 index 0000000000000000000000000000000000000000..b51488b4b81deaa591d560f2b442312c63a018e1 --- /dev/null +++ b/src/main/webapp/scripts/lib/form2js/index.js @@ -0,0 +1,3 @@ +module.exports.form2js = require('./src/form2js'); + +module.exports.js2form = require('./src/js2form'); \ No newline at end of file diff --git a/src/main/webapp/scripts/lib/form2js/license.txt b/src/main/webapp/scripts/lib/form2js/license.txt new file mode 100755 index 0000000000000000000000000000000000000000..1c3cfe4badc912faf2f5e57a073cdf606c2bef3b --- /dev/null +++ b/src/main/webapp/scripts/lib/form2js/license.txt @@ -0,0 +1,19 @@ +Copyright (c) 2010 Maxim Vasiliev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/src/main/webapp/scripts/lib/form2js/package.json b/src/main/webapp/scripts/lib/form2js/package.json new file mode 100755 index 0000000000000000000000000000000000000000..4df9aa2f779fbe35143f22789ca6bb3ed3c9ad99 --- /dev/null +++ b/src/main/webapp/scripts/lib/form2js/package.json @@ -0,0 +1,22 @@ +{ + "name": "form2js", + "version": "1.0.0", + "description": "form2js\r -------", + "main": "index.js", + "directories": { + "example": "example" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://github.com/kirill-zhirnov/form2js.git" + }, + "author": "", + "license": "MIT", + "bugs": { + "url": "https://github.com/kirill-zhirnov/form2js/issues" + }, + "homepage": "https://github.com/kirill-zhirnov/form2js" +} diff --git a/src/main/webapp/scripts/lib/form2js/src/form2js.js b/src/main/webapp/scripts/lib/form2js/src/form2js.js new file mode 100755 index 0000000000000000000000000000000000000000..55c9bac720e93c52afc21b818f1cf1cebb652124 --- /dev/null +++ b/src/main/webapp/scripts/lib/form2js/src/form2js.js @@ -0,0 +1,386 @@ +/** + * Copyright (c) 2010 Maxim Vasiliev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author Maxim Vasiliev + * Date: 09.09.2010 + * Time: 19:02:33 + */ + + +(function (root, factory) +{ + if (typeof exports !== 'undefined' && typeof module !== 'undefined' && module.exports) { + // NodeJS + module.exports = factory(); + } + else if (typeof define === 'function' && define.amd) + { + // AMD. Register as an anonymous module. + define(factory); + } + else + { + // Browser globals + root.form2js = factory(); + } +}(this, function () +{ + "use strict"; + + /** + * Returns form values represented as Javascript object + * "name" attribute defines structure of resulting object + * + * @param rootNode {Element|String} root form element (or it's id) or array of root elements + * @param delimiter {String} structure parts delimiter defaults to '.' + * @param skipEmpty {Boolean} should skip empty text values, defaults to true + * @param nodeCallback {Function} custom function to get node value + * @param useIdIfEmptyName {Boolean} if true value of id attribute of field will be used if name of field is empty + */ + function form2js(rootNode, delimiter, skipEmpty, nodeCallback, useIdIfEmptyName, getDisabled) + { + getDisabled = getDisabled ? true : false; + if (typeof skipEmpty == 'undefined' || skipEmpty == null) skipEmpty = true; + if (typeof delimiter == 'undefined' || delimiter == null) delimiter = '.'; + if (arguments.length < 5) useIdIfEmptyName = false; + + rootNode = typeof rootNode == 'string' ? document.getElementById(rootNode) : rootNode; + + var formValues = [], + currNode, + i = 0; + + /* If rootNode is array - combine values */ + if (rootNode.constructor == Array || (typeof NodeList != "undefined" && rootNode.constructor == NodeList)) + { + while(currNode = rootNode[i++]) + { + formValues = formValues.concat(getFormValues(currNode, nodeCallback, useIdIfEmptyName, getDisabled)); + } + } + else + { + formValues = getFormValues(rootNode, nodeCallback, useIdIfEmptyName, getDisabled); + } + + return processNameValues(formValues, skipEmpty, delimiter); + } + + /** + * Processes collection of { name: 'name', value: 'value' } objects. + * @param nameValues + * @param skipEmpty if true skips elements with value == '' or value == null + * @param delimiter + */ + function processNameValues(nameValues, skipEmpty, delimiter) + { + var result = {}, + arrays = {}, + i, j, k, l, + value, + nameParts, + currResult, + arrNameFull, + arrName, + arrIdx, + namePart, + name, + _nameParts; + + for (i = 0; i < nameValues.length; i++) + { + + value = realValue(nameValues[i].value); + + if (skipEmpty && (value === '' || value === null)) continue; + + name = nameValues[i].name; + _nameParts = name.split(delimiter); + + nameParts = []; + currResult = result; + arrNameFull = ''; + + for(j = 0; j < _nameParts.length; j++) + { + /* Skip first part if empty - effectively stripping the delimiter if used as a prefix */ + if (j === 0 && _nameParts[j] === '') continue; + + namePart = _nameParts[j].split(']['); + if (namePart.length > 1) + { + for(k = 0; k < namePart.length; k++) + { + if (k == 0) + { + namePart[k] = namePart[k] + ']'; + } + else if (k == namePart.length - 1) + { + namePart[k] = '[' + namePart[k]; + } + else + { + namePart[k] = '[' + namePart[k] + ']'; + } + + arrIdx = namePart[k].match(/([a-z_]+)?\[([a-z_][a-z0-9_]+?)\]/i); + if (arrIdx) + { + for(l = 1; l < arrIdx.length; l++) + { + if (arrIdx[l]) nameParts.push(arrIdx[l]); + } + } + else{ + nameParts.push(namePart[k]); + } + } + } + else + nameParts = nameParts.concat(namePart); + } + + for (j = 0; j < nameParts.length; j++) + { + namePart = nameParts[j]; + + if (namePart.indexOf('[]') > -1 && j == nameParts.length - 1) + { + arrName = namePart.substr(0, namePart.indexOf('[')); + arrNameFull += arrName; + + if (!currResult[arrName]) currResult[arrName] = []; + currResult[arrName].push(value); + } + else if (namePart.indexOf('[') > -1) + { + arrName = namePart.substr(0, namePart.indexOf('[')); + arrIdx = namePart.replace(/(^([a-z_]+)?\[)|(\]$)/gi, ''); + + /* Unique array name */ + arrNameFull += '_' + arrName + '_' + arrIdx; + + /* + * Because arrIdx in field name can be not zero-based and step can be + * other than 1, we can't use them in target array directly. + * Instead we're making a hash where key is arrIdx and value is a reference to + * added array element + */ + + if (!arrays[arrNameFull]) arrays[arrNameFull] = {}; + if (arrName != '' && !currResult[arrName]) currResult[arrName] = []; + + if (j == nameParts.length - 1) + { + if (arrName == '') + { + currResult.push(value); + arrays[arrNameFull][arrIdx] = currResult[currResult.length - 1]; + } + else + { + currResult[arrName].push(value); + arrays[arrNameFull][arrIdx] = currResult[arrName][currResult[arrName].length - 1]; + } + } + else + { + if (!arrays[arrNameFull][arrIdx]) + { + if ((/^[0-9a-z_]+\[?/i).test(nameParts[j+1])) currResult[arrName].push({}); + else currResult[arrName].push([]); + + arrays[arrNameFull][arrIdx] = currResult[arrName][currResult[arrName].length - 1]; + } + } + + currResult = arrays[arrNameFull][arrIdx]; + } + else + { + arrNameFull += namePart; + + if (j < nameParts.length - 1) /* Not the last part of name - means object */ + { + if (!currResult[namePart]) currResult[namePart] = {}; + currResult = currResult[namePart]; + } + else + { + currResult[namePart] = value; + } + } + } + } + + return result; + } + + function getFormValues(rootNode, nodeCallback, useIdIfEmptyName, getDisabled) + { + var result = extractNodeValues(rootNode, nodeCallback, useIdIfEmptyName, getDisabled); + return result.length > 0 ? result : getSubFormValues(rootNode, nodeCallback, useIdIfEmptyName, getDisabled); + } + + function getSubFormValues(rootNode, nodeCallback, useIdIfEmptyName, getDisabled) + { + var result = [], + currentNode = rootNode.firstChild; + + while (currentNode) + { + result = result.concat(extractNodeValues(currentNode, nodeCallback, useIdIfEmptyName, getDisabled)); + currentNode = currentNode.nextSibling; + } + + return result; + } + + function extractNodeValues(node, nodeCallback, useIdIfEmptyName, getDisabled) { + if (node.disabled && !getDisabled) return []; + + var callbackResult, fieldValue, result, fieldName = getFieldName(node, useIdIfEmptyName); + + callbackResult = nodeCallback && nodeCallback(node); + + if (callbackResult && callbackResult.name) { + result = [callbackResult]; + } + else if (fieldName != '' && node.nodeName.match(/INPUT|TEXTAREA/i)) { + fieldValue = getFieldValue(node, getDisabled); + if (null === fieldValue) { + result = []; + } else { + result = [ { name: fieldName, value: fieldValue} ]; + } + } + else if (fieldName != '' && node.nodeName.match(/SELECT/i)) { + fieldValue = getFieldValue(node, getDisabled); + result = [ { name: fieldName.replace(/\[\]$/, ''), value: fieldValue } ]; + } + else { + result = getSubFormValues(node, nodeCallback, useIdIfEmptyName, getDisabled); + } + + return result; + } + + function getFieldName(node, useIdIfEmptyName) + { + if (node.name && node.name != '') return node.name; + else if (useIdIfEmptyName && node.id && node.id != '') return node.id; + else return ''; + } + + + function getFieldValue(fieldNode, getDisabled) + { + if (fieldNode.disabled && !getDisabled) return null; + + switch (fieldNode.nodeName) { + case 'INPUT': + case 'TEXTAREA': + switch (fieldNode.type.toLowerCase()) { + case 'radio': + if (fieldNode.checked && fieldNode.value === "false") return false; + case 'checkbox': + if (fieldNode.checked && fieldNode.value === "true") return true; + if (!fieldNode.checked && fieldNode.value === "true") return false; + if (fieldNode.checked) return fieldNode.value; + break; + + case 'button': + case 'reset': + case 'submit': + case 'image': + return ''; + break; + + default: + return fieldNode.value; + break; + } + break; + + case 'SELECT': + return getSelectedOptionValue(fieldNode); + break; + + default: + break; + } + + return null; + } + + function getSelectedOptionValue(selectNode) + { + var multiple = selectNode.multiple, + result = [], + options, + i, l; + + if (!multiple) return selectNode.value; + + for (options = selectNode.getElementsByTagName("option"), i = 0, l = options.length; i < l; i++) + { + if (options[i].selected) result.push(options[i].value); + } + + return result; + } + + function isNumeric( num ) { + // parseFloat NaNs numeric-cast false positives (null|true|false|"") + // ...but misinterprets leading-number strings, particularly hex literals ("0x...") + // subtraction forces infinities to NaN + // adding 1 corrects loss of precision from parseFloat (jQuery issue #15100) + return !Array.isArray( num ) && (num - parseFloat( num ) + 1) >= 0; + } + + function realValue(val, bool){ + var undefined; + // only evaluate strings + if (typeof val != 'string') return val; + if (bool){ + if (val === '0'){ + return false; + } + if (val === '1'){ + return true; + } + } + if (isNumeric(val)){ + return +val; + } + switch(val) { + case 'true': return true; + case 'false': return false; + case 'undefined': return undefined; + case 'null': return null; + default: return val; + } + } + + return form2js; + +})); diff --git a/src/main/webapp/scripts/lib/form2js/src/jquery.toObject.js b/src/main/webapp/scripts/lib/form2js/src/jquery.toObject.js new file mode 100755 index 0000000000000000000000000000000000000000..3e5c9de73e3549ebdeb45abd77a430e8bbefbf5b --- /dev/null +++ b/src/main/webapp/scripts/lib/form2js/src/jquery.toObject.js @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2010 Maxim Vasiliev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author Maxim Vasiliev + * Date: 29.06.11 + * Time: 20:09 + */ + +(function($){ + + /** + * jQuery wrapper for form2object() + * Extracts data from child inputs into javascript object + */ + $.fn.toObject = function(options) + { + var result = [], + settings = { + mode: 'first', // what to convert: 'all' or 'first' matched node + delimiter: ".", + skipEmpty: true, + nodeCallback: null, + useIdIfEmptyName: false + }; + + if (options) + { + $.extend(settings, options); + } + + switch(settings.mode) + { + case 'first': + return form2js(this.get(0), settings.delimiter, settings.skipEmpty, settings.nodeCallback, settings.useIdIfEmptyName); + break; + case 'all': + this.each(function(){ + result.push(form2js(this, settings.delimiter, settings.skipEmpty, settings.nodeCallback, settings.useIdIfEmptyName)); + }); + return result; + break; + case 'combine': + return form2js(Array.prototype.slice.call(this), settings.delimiter, settings.skipEmpty, settings.nodeCallback, settings.useIdIfEmptyName); + break; + } + } + +})(jQuery); \ No newline at end of file diff --git a/src/main/webapp/scripts/lib/form2js/src/js2form.js b/src/main/webapp/scripts/lib/form2js/src/js2form.js new file mode 100755 index 0000000000000000000000000000000000000000..03408e1690f0c5cc5833f62c39527a2cbae5bcf2 --- /dev/null +++ b/src/main/webapp/scripts/lib/form2js/src/js2form.js @@ -0,0 +1,330 @@ +/** + * Copyright (c) 2010 Maxim Vasiliev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author Maxim Vasiliev + * Date: 19.09.11 + * Time: 23:40 + */ + +(function (root, factory) +{ + if (typeof exports !== 'undefined' && typeof module !== 'undefined' && module.exports) { + // NodeJS + module.exports = factory(); + } + else if (typeof define === 'function' && define.amd) + { + // AMD. Register as an anonymous module. + define(factory); + } + else + { + // Browser globals + root.js2form = factory(); + } +}(this, function () +{ + "use strict"; + + var _subArrayRegexp = /^\[\d+?\]/, + _subObjectRegexp = /^[a-zA-Z_][a-zA-Z_0-9]*/, + _arrayItemRegexp = /\[[0-9]+?\]$/, + _lastIndexedArrayRegexp = /(.*)(\[)([0-9]*)(\])$/, + _arrayOfArraysRegexp = /\[([0-9]+)\]\[([0-9]+)\]/g, + _inputOrTextareaRegexp = /INPUT|TEXTAREA/i; + + /** + * + * @param rootNode + * @param data + * @param delimiter + * @param nodeCallback + * @param useIdIfEmptyName + */ + function js2form(rootNode, data, delimiter, nodeCallback, useIdIfEmptyName) + { + if (arguments.length < 3) delimiter = '.'; + if (arguments.length < 4) nodeCallback = null; + if (arguments.length < 5) useIdIfEmptyName = false; + + rootNode = (typeof rootNode == 'string') ? document.getElementById(rootNode) : rootNode; + + var fieldValues, + formFieldsByName; + + fieldValues = object2array(data); + formFieldsByName = getFields(rootNode, useIdIfEmptyName, delimiter, {}, true); + + for (var i = 0; i < fieldValues.length; i++) + { + var fieldName = fieldValues[i].name, + fieldValue = fieldValues[i].value; + + if (typeof formFieldsByName[fieldName] != 'undefined') + { + setValue(formFieldsByName[fieldName], fieldValue); + } + else if (typeof formFieldsByName[fieldName.replace(_arrayItemRegexp, '[]')] != 'undefined') + { + setValue(formFieldsByName[fieldName.replace(_arrayItemRegexp, '[]')], fieldValue); + } + else if (typeof formFieldsByName[fieldName.replace(_arrayItemRegexp, "")] != 'undefined') { + setValue(formFieldsByName[fieldName.replace(_arrayItemRegexp, "")], fieldValue); + } + } + } + + function setValue(field, value) + { + var children, i, l; + + if (field instanceof Array) + { + for(i = 0; i < field.length; i++) + { + if (field[i].value == value || value === true) field[i].checked = true; + } + } + else if (_inputOrTextareaRegexp.test(field.nodeName)) + { + field.value = value; + } + else if (/SELECT/i.test(field.nodeName)) + { + children = field.getElementsByTagName('option'); + for (i = 0,l = children.length; i < l; i++) + { + if (children[i].value == value) + { + children[i].selected = true; + if (field.multiple) break; + } + else if (!field.multiple) + { + children[i].selected = false; + } + } + } + } + + function getFields(rootNode, useIdIfEmptyName, delimiter, arrayIndexes, shouldClean) + { + if (arguments.length < 4) arrayIndexes = {}; + + var result = {}, + currNode = rootNode.firstChild, + name, nameNormalized, + subFieldName, + i, j, l, + options; + + while (currNode) + { + name = ''; + + if (currNode.name && currNode.name != '') + { + name = currNode.name; + } + else if (useIdIfEmptyName && currNode.id && currNode.id != '') + { + name = currNode.id; + } + + if (name == '') + { + var subFields = getFields(currNode, useIdIfEmptyName, delimiter, arrayIndexes, shouldClean); + for (subFieldName in subFields) + { + if (typeof result[subFieldName] == 'undefined') + { + result[subFieldName] = subFields[subFieldName]; + } + else + { + for (i = 0; i < subFields[subFieldName].length; i++) + { + result[subFieldName].push(subFields[subFieldName][i]); + } + } + } + } + else + { + if (/SELECT/i.test(currNode.nodeName)) + { + for(j = 0, options = currNode.getElementsByTagName('option'), l = options.length; j < l; j++) + { + if (shouldClean) + { + options[j].selected = false; + } + + nameNormalized = normalizeName(name, delimiter, arrayIndexes); + result[nameNormalized] = currNode; + } + } + else if (/INPUT/i.test(currNode.nodeName) && /CHECKBOX|RADIO/i.test(currNode.type)) + { + if(shouldClean) + { + currNode.checked = false; + } + + nameNormalized = normalizeName(name, delimiter, arrayIndexes); + nameNormalized = nameNormalized.replace(_arrayItemRegexp, '[]'); + if (!result[nameNormalized]) result[nameNormalized] = []; + result[nameNormalized].push(currNode); + } + else + { + if (shouldClean) + { + currNode.value = ''; + } + + nameNormalized = normalizeName(name, delimiter, arrayIndexes); + result[nameNormalized] = currNode; + } + } + + currNode = currNode.nextSibling; + } + + return result; + } + + /** + * Normalizes names of arrays, puts correct indexes (consecutive and ordered by element appearance in HTML) + * @param name + * @param delimiter + * @param arrayIndexes + */ + function normalizeName(name, delimiter, arrayIndexes) + { + var nameChunksNormalized = [], + nameChunks = name.split(delimiter), + currChunk, + nameMatches, + nameNormalized, + currIndex, + newIndex, + i; + + name = name.replace(_arrayOfArraysRegexp, '[$1].[$2]'); + for (i = 0; i < nameChunks.length; i++) + { + currChunk = nameChunks[i]; + nameChunksNormalized.push(currChunk); + nameMatches = currChunk.match(_lastIndexedArrayRegexp); + if (nameMatches != null) + { + nameNormalized = nameChunksNormalized.join(delimiter); + currIndex = nameNormalized.replace(_lastIndexedArrayRegexp, '$3'); + nameNormalized = nameNormalized.replace(_lastIndexedArrayRegexp, '$1'); + + if (typeof (arrayIndexes[nameNormalized]) == 'undefined') + { + arrayIndexes[nameNormalized] = { + lastIndex: -1, + indexes: {} + }; + } + + if (currIndex == '' || typeof arrayIndexes[nameNormalized].indexes[currIndex] == 'undefined') + { + arrayIndexes[nameNormalized].lastIndex++; + arrayIndexes[nameNormalized].indexes[currIndex] = arrayIndexes[nameNormalized].lastIndex; + } + + newIndex = arrayIndexes[nameNormalized].indexes[currIndex]; + nameChunksNormalized[nameChunksNormalized.length - 1] = currChunk.replace(_lastIndexedArrayRegexp, '$1$2' + newIndex + '$4'); + } + } + + nameNormalized = nameChunksNormalized.join(delimiter); + nameNormalized = nameNormalized.replace('].[', ']['); + return nameNormalized; + } + + function object2array(obj, lvl) + { + var result = [], i, name; + + if (arguments.length == 1) lvl = 0; + + if (obj == null) + { + result = [{ name: "", value: null }]; + } + else if (typeof obj == 'string' || typeof obj == 'number' || typeof obj == 'date' || typeof obj == 'boolean') + { + result = [ + { name: "", value : obj } + ]; + } + else if (obj instanceof Array) + { + for (i = 0; i < obj.length; i++) + { + name = "[" + i + "]"; + result = result.concat(getSubValues(obj[i], name, lvl + 1)); + } + } + else + { + for (i in obj) + { + name = i; + result = result.concat(getSubValues(obj[i], name, lvl + 1)); + } + } + + return result; + } + + function getSubValues(subObj, name, lvl) + { + var itemName; + var result = [], tempResult = object2array(subObj, lvl + 1), i, tempItem; + + for (i = 0; i < tempResult.length; i++) + { + itemName = name; + if (_subArrayRegexp.test(tempResult[i].name)) + { + itemName += tempResult[i].name; + } + else if (_subObjectRegexp.test(tempResult[i].name)) + { + itemName += '.' + tempResult[i].name; + } + + tempItem = { name: itemName, value: tempResult[i].value }; + result.push(tempItem); + } + + return result; + } + + return js2form; + +})); diff --git a/src/main/webapp/scripts/lib/spawn/spawn.js b/src/main/webapp/scripts/lib/spawn/spawn.js index 7b2f64c0629afaea931e3798eaf08ef79a6f8eb7..be270f8641faf1cb2a948dbda49de2bd9022c167 100644 --- a/src/main/webapp/scripts/lib/spawn/spawn.js +++ b/src/main/webapp/scripts/lib/spawn/spawn.js @@ -148,17 +148,28 @@ var el, $el, parts, id, classes, tagParts, attrs, isVoid, // property names to skip later skip = [ - 'innerHTML', 'html', 'attr', 'config', 'kind', 'prepend', 'append', 'appendTo', - 'classes', 'className', 'addClass', 'style', 'data', 'fn', 'label' + 'innerHTML', 'html', 'attr', 'config', + 'kind', 'tag', 'tagName', + 'prepend', 'append', 'appendTo', + 'classes', 'className', 'addClass', + 'style', 'data', 'fn', 'label', 'done' ], errors = []; // collect errors - // deal with passing an array as the only argument + // handle passing an array with the arguments if (Array.isArray(tag)){ children = tag[2]; opts = tag[1]; tag = tag[0]; } + + // handle passing a config object as the only argument + if (!children && !opts && typeof tag !== 'string') { + opts = tag; + tag = null; + } + + tag = tag || opts.tag || opts.tagName || 'span'; if (tag === '!'){ el = doc.createDocumentFragment(); @@ -321,9 +332,9 @@ } // special handling of 'append' property - if (opts.append){ + if (opts.append || opts.insert){ try { - appendChildren(el, opts.append, spawn) + appendChildren(el, opts.append || opts.insert, spawn) } catch(e){ // write error to console @@ -380,8 +391,12 @@ } if (opts.after){ - frag.appendChild(el); - appendChildren(frag, opts.after, spawn); + // don't append element twice if + // there's 'before' AND 'after' + if (!opts.before) { + appendChildren(frag, opts.after, spawn); + frag.appendChild(el); + } el = frag; } @@ -394,10 +409,17 @@ el.element = el; - el.get = function(){ + el.get = function(callback){ + if (typeof callback == 'function') { + callback.call(el); + } return el; }; + if (typeof opts.done == 'function') { + opts.done.call(el); + } + return el; } diff --git a/src/main/webapp/scripts/utils.js b/src/main/webapp/scripts/utils.js index afd533031af01737400c998076e75ad55afdb9fb..f65aa438aa768897051fa560598d8d4c209d67a9 100755 --- a/src/main/webapp/scripts/utils.js +++ b/src/main/webapp/scripts/utils.js @@ -299,9 +299,9 @@ jQuery.fn.tableSort = function(){ if ($tr.find('> th, > td').first().hasClass('index')) return; $tr.prepend('<td class="index hidden" style="display:none;">' + i + '</td>'); }); - $table.find('th').not('.sort').filter(function(){ - return this.innerHTML.trim() > ''; - }).addClass('sort'); + // $table.find('th').not('.sort').filter(function(){ + // return this.innerHTML.trim() > ''; + // }).addClass('sort'); $table.find('th.sort') .append('<i> </i>') // wrapInner('<a href="#" class="nolink" title="click to sort on this column"/>'). @@ -309,14 +309,14 @@ jQuery.fn.tableSort = function(){ // don't overwrite existing title this.title += ' (click to sort) '; $(this).on('click.sort', function(){ - var $this = $(this), - thIndex = $this.index(), - sorted = $this.hasAnyClass('asc desc'), + var $th = $(this), + thIndex = $th.index(), + sorted = $th.hasAnyClass('asc desc'), sortOrder = 1, sortClass = 'asc'; if (sorted) { // if already sorted, switch to descending order - if ($this.hasClass('asc')) { + if ($th.hasClass('asc')) { sortClass = 'desc'; } else { @@ -325,7 +325,7 @@ jQuery.fn.tableSort = function(){ } } $table.find('th.sort').removeClass('asc desc'); - $this.addClass(sortClass); + $th.addClass(sortClass); sortOrder = (sortClass === 'desc') ? -1 : 1; sorted = !!sortClass; $table.find('td').filter(function(){ diff --git a/src/main/webapp/scripts/xmodal-v1/xmodal.css b/src/main/webapp/scripts/xmodal-v1/xmodal.css index afad8ce39ad7d5bfb843ab284c81ab92e4ece2cf..b5b189a68c4760477914f2042e4e50c1677e5399 100644 --- a/src/main/webapp/scripts/xmodal-v1/xmodal.css +++ b/src/main/webapp/scripts/xmodal-v1/xmodal.css @@ -40,7 +40,7 @@ div.xmodal .title { height: 40px; padding: 0 ; overflow: hidden ; color: #303030 ; background: #f0f0f0 ; border-bottom: 1px solid #e0e0e0 ; } div.xmodal .title .inner { - display: inline-block; padding-left: 10px ; overflow: hidden; white-space: nowrap; + display: inline-block; padding-left: 15px ; overflow: hidden; white-space: nowrap; font-size: 15px ; font-weight: normal ; line-height: 42px ; vertical-align: middle ; } div.xmodal .title .inner > i { color: #505050; font-weight: normal; font-style: normal; } div.xmodal .title .close, @@ -55,7 +55,11 @@ div.xmodal .title .close:hover, div.xmodal .title .maximize:hover { color: #606060; background: #fafafa; border-color: #a0a0a0; cursor: pointer; } div.xmodal .body { width: 100% ; height: 100% ; position: absolute ; overflow: hidden ; } -div.xmodal .body.scroll { overflow-y: auto ; } +div.xmodal .body.scroll, +div.xmodal .body.scroll > .inner { overflow: auto ; } +div.xmodal .body > .inner > iframe { + width: 100%; height: 100%; position: absolute; + top: 0; bottom: 0; right: 0; left: 0; } div.xmodal .body .inner { display: none ; padding: 20px ; font-size: 13px ; line-height: 17px ; } div.xmodal.nopad .body .inner { padding: 0 ; } div.xmodal.open .body .inner { display: block ; } diff --git a/src/main/webapp/scripts/xmodal-v1/xmodal.js b/src/main/webapp/scripts/xmodal-v1/xmodal.js index 41a6bee02edd198fab05d29afa4c9262c9b18475..b56613aafb5ee7f8e57c5bb69b90ea12356d745e 100644 --- a/src/main/webapp/scripts/xmodal-v1/xmodal.js +++ b/src/main/webapp/scripts/xmodal-v1/xmodal.js @@ -901,6 +901,7 @@ if (typeof jQuery == 'undefined') { if ( isDefined(modal.padding||undefined) ) { modal.style = getObject(modal.style||{}); modal.style.padding = modal.padding + 'px'; + modal.$modal.find('> .body').css(modal.style); } if ( isDefined(modal.style||undefined) ) { modal.css = getObject(modal.css||{}); diff --git a/src/main/webapp/scripts/xnat/admin/dicomScpManager.js b/src/main/webapp/scripts/xnat/admin/dicomScpManager.js index e708a9cbd52f58d5c88123722d36aa90faf0552e..f66d5c8eb9433303358fdb90f02cb1626d220ffd 100644 --- a/src/main/webapp/scripts/xnat/admin/dicomScpManager.js +++ b/src/main/webapp/scripts/xnat/admin/dicomScpManager.js @@ -52,7 +52,12 @@ var XNAT = getObject(XNAT || {}); } }) } - + + function scpUrl(appended){ + appended = isDefined(appended) ? '/' + appended : ''; + return rootUrl('/xapi/dicomscp' + appended); + } + // keep track of used ports to help prevent port conflicts dicomScpManager.usedPorts = []; @@ -65,14 +70,14 @@ var XNAT = getObject(XNAT || {}); dicomScpManager.usedPorts = []; dicomScpManager.ids = []; return XNAT.xhr.get({ - url: rootUrl('/xapi/dicomscp'), + url: scpUrl(), dataType: 'json', success: function(data){ dicomScpManager.receivers = data; // refresh the 'usedPorts' array every time this function is called data.forEach(function(item){ dicomScpManager.usedPorts.push(item.port); - dicomScpManager.ids.push(item.scpId); + dicomScpManager.ids.push(item.id); }); callback.apply(this, arguments); } @@ -83,7 +88,7 @@ var XNAT = getObject(XNAT || {}); if (!id) return null; callback = isFunction(callback) ? callback : function(){}; return XNAT.xhr.get({ - url: rootUrl('/xapi/dicomscp/' + id), + url: scpUrl(id), dataType: 'json', success: callback }); @@ -97,52 +102,45 @@ var XNAT = getObject(XNAT || {}); }; // dialog to create/edit receivers - dicomScpManager.dialog = function(item, opts){ + dicomScpManager.dialog = function(item, isNew){ var tmpl = $('#dicom-scp-editor-template'); var doWhat = !item ? 'New' : 'Edit'; - var isNew = doWhat === 'New'; + var oldPort = item && item.port ? item.port : null; + isNew = firstDefined(isNew, doWhat === 'New'); + console.log(isNew); item = item || {}; xmodal.open({ title: doWhat + ' DICOM SCP Receiver', template: tmpl.clone(), - height: 400, + width: 350, + height: 300, + scroll: false, padding: '0', beforeShow: function(obj){ var $form = obj.$modal.find('#dicom-scp-editor-panel'); - if (item && item.scpId) { - // check the 'enabled' checkbox for new items + if (item && isDefined(item.id)) { $form.setValues(item); - // forOwn(item, function(prop, val){ - // $form.find('[name="'+prop+'"]').val(val); - // }); } - //if (isNew) { - // $form.find('#scp-enabled').prop('checked', true); - // // $$('?enabled')[0].checked = true; - //} }, okClose: false, okLabel: 'Save', okAction: function(obj){ // the form panel is 'dicomScpEditorTemplate' in site-admin-element.yaml var $form = obj.$modal.find('#dicom-scp-editor-panel'); - var id = $form.find('#scp-id').val(); - if (!id) { - xmodal.message('SCP ID is required'); - return false; - } + var $title = $form.find('#scp-title'); + var $port = $form.find('#scp-port'); + console.log(item.id); $form.submitJSON({ - method: 'PUT', - url: '/xapi/dicomscp/' + id, + method: isNew ? 'POST' : 'PUT', + url: isNew ? scpUrl() : scpUrl(item.id), validate: function(){ + $form.find(':input').removeClass('invalid'); - var $id = $form.find('#scp-id'); - var $port = $form.find('#scp-port'); - var $title = $form.find('#scp-title'); + var errors = 0; var errorMsg = 'Errors were found with the following fields: <ul>'; - [$id, $port, $title].forEach(function($el){ + [$port, $title].forEach(function($el){ var el = $el[0]; if (!el.value) { errors++; @@ -151,23 +149,26 @@ var XNAT = getObject(XNAT || {}); } }); - if (isNew) { - if (dicomScpManager.ids.indexOf($id.val()) > -1) { - errors++; - errorMsg += '<li><b>SCP ID</b> already exists. Please use a different ID value.</li>'; - $id.addClass('invalid'); - } - if (dicomScpManager.usedPorts.indexOf($port.val()) > -1) { - errors++; - errorMsg += '<li><b>Port</b> is already in use. Please use another port number.</li>'; - $port.addClass('invalid'); - } + var newPort = $port.val(); + + console.log(newPort); + + // only check for port conflicts if we're changing the port + if (newPort+'' !== oldPort+''){ + dicomScpManager.usedPorts.forEach(function(usedPort){ + if (usedPort+'' === newPort+''){ + errors++; + errorMsg += '<li>Port <b>' + newPort + '</b> is already in use. Please use another port number.</li>'; + $port.addClass('invalid'); + return false; + } + }); } errorMsg += '</ul>'; if (errors > 0) { - xmodal.message('Errors Found', errorMsg); + xmodal.message('Errors Found', errorMsg, { height: 300 }); } return errors === 0; @@ -199,10 +200,8 @@ var XNAT = getObject(XNAT || {}); // add table header row scpTable.tr() .th({ addClass: 'left', html: '<b>AE Title</b>' }) - .th({ addClass: 'left', html: '<b>SCP ID</b>' }) .th('<b>Port</b>') .th('<b>Enabled</b>') - //.th('<b>Default?</b>') // if this is enabled, enable the radio button(s) too (below) .th('<b>Actions</b>'); // TODO: move event listeners to parent elements - events will bubble up @@ -216,9 +215,11 @@ var XNAT = getObject(XNAT || {}); // save the status when clicked var enabled = this.checked; XNAT.xhr.put({ - url: rootUrl('/xapi/dicomscp/' + item.scpId + '/enabled/' + enabled), + url: scpUrl(item.id + '/enabled/' + enabled), success: function(){ - console.log(item.scpId + (enabled ? ' enabled' : ' disabled')) + var status = (enabled ? ' enabled' : ' disabled'); + XNAT.ui.banner.top(1000, '<b>' + item.aeTitle + '</b> ' + status, 'success'); + console.log(item.id + (enabled ? ' enabled' : ' disabled')) } }); } @@ -230,24 +231,36 @@ var XNAT = getObject(XNAT || {}); return spawn('a.link|href=#!', { onclick: function(e){ e.preventDefault(); - dicomScpManager.dialog(item); + dicomScpManager.dialog(item, false); } }, [['b', text]]); } + function editButton(item) { + return spawn('button.btn.sm.edit', { + onclick: function(e){ + e.preventDefault(); + dicomScpManager.dialog(item, false); + } + }, 'Edit'); + } + function deleteButton(item){ return spawn('button.btn.sm.delete', { onclick: function(){ xmodal.confirm({ + height: 220, + scroll: false, content: "" + - "<p>Are you sure you'd like to delete the '" + item.aeTitle + "' DICOM Receiver?</p>" + + "<p>Are you sure you'd like to delete the '<b>" + item.aeTitle + "</b>' DICOM Receiver?</p>" + "<p><b>This action cannot be undone.</b></p>", okAction: function(){ + console.log('delete id ' + item.id); XNAT.xhr.delete({ - url: rootUrl('/xapi/dicomscp/' + item.scpId), + url: scpUrl(item.id), success: function(){ - console.log('"'+ item.scpId + '" deleted'); - XNAT.ui.banner.top(2000, '<b>"'+ item.scpId + '"</b> deleted.', 'success'); + console.log('"'+ item.aeTitle + '" deleted'); + XNAT.ui.banner.top(1000, '<b>"'+ item.aeTitle + '"</b> deleted.', 'success'); refreshTable(); } }); @@ -259,19 +272,11 @@ var XNAT = getObject(XNAT || {}); dicomScpManager.getAll().done(function(data){ data.forEach(function(item){ - scpTable.tr({title:item.scpId}) + scpTable.tr({ title: item.aeTitle, data: { id: item.id, port: item.port }}) .td([editLink(item, item.aeTitle)]) - .td(item.scpId) .td([['div.mono.center', item.port]]) .td([enabledCheckbox(item)]) - .td([['div.center', [deleteButton(item)]]]); - // scpTable.row([ - // item.aeTitle, - // [['div.mono.center', item.port]], - // [enabledCheckbox(item)], - // //[['div.center', [['input|type=radio;name=defaultReceiver']] ]], // how do we know which one is 'default' - // [['div.center', [editButton(item), spacer(10), deleteButton(item)]]] - // ]); + .td([['div.center', [editButton(item), spacer(10), deleteButton(item)]]]); }); if (container){ @@ -287,7 +292,6 @@ var XNAT = getObject(XNAT || {}); dicomScpManager.$table = $(scpTable.table); return scpTable.table; - }; dicomScpManager.init = function(container){ @@ -302,7 +306,7 @@ var XNAT = getObject(XNAT || {}); var newReceiver = spawn('button.new-dicomscp-receiver.btn.btn-sm.submit', { html: 'New DICOM SCP Receiver', onclick: function(){ - dicomScpManager.dialog(); + dicomScpManager.dialog(null, true); } }); @@ -310,7 +314,7 @@ var XNAT = getObject(XNAT || {}); html: 'Start All', onclick: function(){ XNAT.xhr.put({ - url: XNAT.url.rootUrl('/xapi/dicomscp/start'), + url: scpUrl('start'), success: function(){ console.log('DICOM SCP Receivers started') } @@ -322,7 +326,7 @@ var XNAT = getObject(XNAT || {}); html: 'Stop All', onclick: function(){ XNAT.xhr.put({ - url: XNAT.url.rootUrl('/xapi/dicomscp/stop'), + url: scpUrl('stop'), success: function(){ console.log('DICOM SCP Receivers stopped') } @@ -354,8 +358,8 @@ var XNAT = getObject(XNAT || {}); dicomScpManager.$container.prepend(table); }); } - dicomScpManager.refresh = refreshTable; + dicomScpManager.refresh = refreshTable; dicomScpManager.init(); diff --git a/src/main/webapp/scripts/xnat/admin/pwExpType.js b/src/main/webapp/scripts/xnat/admin/pwExpType.js index d488379e5d755266e3cbfd5d65ea7bbca9fefd3c..029720d556e4c13aaa6ce1a5d950c2b52ed56809 100644 --- a/src/main/webapp/scripts/xnat/admin/pwExpType.js +++ b/src/main/webapp/scripts/xnat/admin/pwExpType.js @@ -7,15 +7,13 @@ fieldDate.attr('placeholder', 'MM/DD/YYYY'); openCal = $('#openCal-passwordExpirationDate'); openCal.click(openCalendar); - fieldInterval[0].style.width = '40px'; - fieldInterval[0].style.textAlign = 'right'; fieldInterval[0].style.marginTop='10px'; fieldDate[0].style.width = '90px'; fieldDate[0].style.marginTop='10px'; fieldDate.datetimepicker({ timepicker:false, format:'m/d/Y', -// minDate:'-1970/01/01' // today is minimum date + maxDate:'1970/01/01' // today is max date, disallow future date selection }); sdtDisabled = $('#passwordExpirationTypeDisabled'); sdtInterval = $('#passwordExpirationTypeInterval'); diff --git a/src/main/webapp/scripts/xnat/app/codeEditor.js b/src/main/webapp/scripts/xnat/app/codeEditor.js new file mode 100644 index 0000000000000000000000000000000000000000..15303d04a76c0f3cfc27af878333a38c47c0ebf4 --- /dev/null +++ b/src/main/webapp/scripts/xnat/app/codeEditor.js @@ -0,0 +1,225 @@ +/** + * functions for XNAT generic code editor + * xnat-templates/screens/Scripts.vm + */ + +var XNAT = getObject(XNAT || {}); + +(function(XNAT){ + + var codeEditor, + xhr = XNAT.xhr; + + var csrfParam = { + XNAT_CSRF: csrfToken + }; + + XNAT.app = getObject(XNAT.app || {}); + + XNAT.app.codeEditor = + codeEditor = getObject(XNAT.app.codeEditor || {}); + + /** + * Main Editor constructor + * @param source String/Element - CSS selector, DOM object or jQuery object + * @param opts Object - configuration object + * @constructor + */ + function Editor(source, opts){ + + var _this = this; + + this.opts = cloneObject(opts); + + this.source = source; + this.$source = $$(this.source); + + // this will be defined when the dialog opens + this.$editor = null; + + this.isInput = (function(){ return _this.$source.is(':input') })(); + + this.isUrl = !this.source && this.opts.url; + + // set default language for editor + // add [data-code-language="javascript"] to source code element + // for correct syntax highlighting + this.language = this.opts.language || this.$source.attr('data-code-language'); + + this.getSourceCode = function(){ + if (this.isUrl){ + // set source to null or empty string + // and opts.url = '/url/to/data' to + // pull code from a REST call + this.code = ''; + } + else { + // extract code from the source + this.code = this.isInput ? this.$source.val() : this.$source.html(); + } + return this.code + }; + + // + this.getSourceCode(); + + }; + + Editor.fn = Editor.prototype; + + Editor.fn.getValue = function(editor){ + this.$editor = editor || this.$editor; + this.code = this.$editor ? this.aceEditor.getValue() : this.getSourceCode(); + return this; + }; + + Editor.fn.save = function(method, url, opts){ + + var _this = this; + + // call this on save to make sure we have the latest edits + this.getValue(); + + if (this.isUrl){ + // save via ajax + return xhr.request(extend(true, { + method: method, + url: url, + success: function(){ + _this.dialog.close() + } + }, opts)) + } + else { + // otherwise put the modified code + // back where it came from + if (this.isInput) { + this.$source.val(this.code); + } + else { + this.$source.text(this.code); + } + this.dialog.close() + } + + return this; + + }; + + Editor.fn.load = function(){ + + var _this = this; + + _this.code = _this.getSourceCode(); + + var editor = spawn('div', { + className: 'editor-content', + html: '', + style: { + position: 'absolute', + top: 0, right: 0, bottom: 0, left: 0, + border: '1px solid #ccc' + }, + done: function(){ + _this.aceEditor = ace.edit(this); // {this} is the <div> being spawned here + _this.aceEditor.setTheme('ace/theme/eclipse'); + _this.aceEditor.getSession().setMode('ace/mode/' + stringLower(_this.language||'')); + _this.aceEditor.session.setValue(_this.code); + } + }); + + // put the new editor div in the wrapper + _this.$editor.empty().append(editor); + + return this; + + }; + + Editor.fn.revert = function(){ + // TODO: reload original content + }; + + Editor.fn.closeEditor = function(){ + this.dialog.close(); + return this; + }; + + // open code in a dialog for editing + Editor.fn.openEditor = function(opts){ + + var _this = this; + + var modal = {}; + modal.width = 880; + modal.height = 580; + modal.scroll = false; + modal.content = ''; + + opts = cloneObject(opts); + + // insert additional content above editor + if (opts.before || opts.contentTop) { + modal.content += opts.before || opts.contentTop; + } + + // div container for code editor + modal.content += '<div class="code-editor" style="width:840px;height:440px;position:relative;"></div>'; + + // insert additional content BELOW editor + if (opts.after || opts.contentBottom) { + modal.content += opts.after || opts.contentBottom; + } + + modal.title = 'XNAT Code Editor'; + modal.title += (_this.language) ? ' - ' + _this.language : ''; + // modal.closeBtn = false; + // modal.maximize = true; + modal.esc = false; // prevent closing on 'esc' + modal.enter = false; // prevents modal closing on 'enter' keypress + modal.footerContent = '<span style="color:#555;">' + + (_this.isUrl ? + 'Changes will be submitted on save.' : + 'Changes are not submitted automatically.<br>The containing form will need to be submitted to save.') + + '</span>'; + modal.beforeShow = function(obj){ + _this.$editor = obj.$modal.find('div.code-editor'); + _this.load(); + }; + modal.afterShow = function(){ + _this.$editor.focus(); + }; + modal.buttons = { + save: { + label: _this.isUrl ? 'Submit Changes' : 'Apply Changes', + action: function(){ + _this.save(); + }, + isDefault: true, + close: false + }, + close: { + label: 'Cancel' + } + }; + + // override modal options with {opts} + extend(modal, opts); + + this.dialog = xmodal.open(modal); + + return this; + + }; + + /** + * Open a code editor dialog and apply edits to source. If source is a url, submit changes on close. + * @param source String/Element - CSS selector string or DOM element that contains the source code to edit + * @param opts - Object - config object + * @returns {Editor} + */ + codeEditor.init = function(source, opts){ + return new Editor(source, opts); + }; + +})(XNAT); + diff --git a/src/main/webapp/scripts/xnat/app/eventsManager.js b/src/main/webapp/scripts/xnat/app/eventsManager.js index 00380cfe88f57e8600dbcfdd69c21f9f2b480d6e..8cc0599dd798270221ec81aec7554af229f4ff2d 100644 --- a/src/main/webapp/scripts/xnat/app/eventsManager.js +++ b/src/main/webapp/scripts/xnat/app/eventsManager.js @@ -237,14 +237,20 @@ $(function(){ if (typeof filterableFields !== 'undefined') { for (var filterable in filterableFields) { if (!filterableFields.hasOwnProperty(filterable)) continue; - var filterableVals = filterableFields[filterable]; + var filterableInfo = filterableFields[filterable]; + var filterableVals = filterableInfo["filterValues"]; + var filterableRequired = filterableInfo["filterRequired"]; + var filterableDefault = filterableInfo["defaultValue"]; + if (typeof filterableRequired !== 'undefined' && !filterableRequired) { + filterableHtml = filterableHtml + '<option value=""><NONE></option>'; + } 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">'; for (var i=0; i<filterableVals.length; i++) { - if (filterableVals[i]!=='_FILTER_NOT_REQUIRED_') { - filterableHtml = filterableHtml + '<option value="' + filterableVals[i] + '">' + filterableVals[i] + '</option>'; + if (typeof filterableDefault !== 'undefined' && filterableDefault == filterableVals[i]) { + filterableHtml = filterableHtml + '<option value="' + filterableVals[i] + '" selected>' + filterableVals[i] + '</option>'; } else { - filterableHtml = filterableHtml + '<option value=""><NONE></option>'; + filterableHtml = filterableHtml + '<option value="' + filterableVals[i] + '">' + filterableVals[i] + '</option>'; } } filterableHtml = filterableHtml + '</select> <input type="text" id="filter_input_' + filterable + '" name="' + filterable + diff --git a/src/main/webapp/scripts/xnat/app/scriptEditor.js b/src/main/webapp/scripts/xnat/app/scriptEditor.js index 4d82de62524e20a475789e3c225c7b35840230dc..fd30900130a4584a33bbdd77b8a7bef29123ed56 100644 --- a/src/main/webapp/scripts/xnat/app/scriptEditor.js +++ b/src/main/webapp/scripts/xnat/app/scriptEditor.js @@ -3,18 +3,22 @@ * xnat-templates/screens/Scripts.vm */ -var XNAT = getObject(XNAT||{}); +var XNAT = getObject(XNAT || {}); (function(XNAT){ var app, scriptEditor, xhr = XNAT.xhr; + var csrfParam = { + XNAT_CSRF: csrfToken + }; + XNAT.app = - app = getObject(XNAT.app||{}); + app = getObject(XNAT.app || {}); XNAT.app.scriptEditor = - scriptEditor = getObject(XNAT.app.scriptEditor||{}); + scriptEditor = getObject(XNAT.app.scriptEditor || {}); scriptEditor.scriptIds = []; scriptEditor.languages = scriptEditor.runners = []; @@ -41,12 +45,13 @@ var XNAT = getObject(XNAT||{}); }; // return script url with common parts pre-defined - function scriptURL( scriptId, params ){ - return XNAT.url.restUrl('/data/automation/scripts/' + scriptId, params); + function scriptURL(scriptId, params){ + scriptId = (scriptId) ? '/' + scriptId : ''; + return XNAT.url.restUrl('/data/automation/scripts' + scriptId, params); } // return script url with common parts pre-defined - function scriptVersionsURL( scriptId, params ){ + function scriptVersionsURL(scriptId, params){ return XNAT.url.restUrl('/data/automation/scriptVersions/' + scriptId, params); } @@ -56,7 +61,7 @@ var XNAT = getObject(XNAT||{}); var langOptions = '<option value="!">Select a Language</option>'; - if (langs.length){ + if (langs.length) { forEach(langs, function(lang){ langOptions += '<option value="' + lang + '">' + lang + '</option>'; @@ -69,25 +74,25 @@ var XNAT = getObject(XNAT||{}); scriptEditor.loadScripts = function(){ - var rowTemplate = $('#script-row-template').find('> tbody').html(), + var rowTemplate = $('#script-row-template').find('> tbody').html(), scriptsTable = $('#scripts-table'), - noScripts = $('#no-scripts-installed'); + noScripts = $('#no-scripts-installed'); - xhr.getJSON(XNAT.url.restUrl('/data/automation/scripts'), function(json){ + xhr.getJSON(scriptURL(), function(json){ var scripts = json.ResultSet.Result, - list = ''; + list = ''; // reset id array scriptEditor.scriptIds = []; - if (scripts.length){ + if (scripts.length) { $.each(scripts, function(i, script){ scriptEditor.scriptIds.push(script['Script ID']); list += - rowTemplate. - replace(/__SCRIPT_ID__/g, script['Script ID']). - replace(/__SCRIPT_DESCRIPTION__/g, XNAT.utils.escapeXML(script['Description'])); + rowTemplate.replace(/__SCRIPT_ID__/g, script['Script ID']) + .replace(/__SCRIPT_LABEL__/g, script['Script Label']) + .replace(/__SCRIPT_DESCRIPTION__/g, XNAT.utils.escapeXML(script['Description'])); }); scriptsTable.find('> tbody').html(list); scriptsTable.show(); @@ -102,27 +107,27 @@ var XNAT = getObject(XNAT||{}); }; - function saveScript( scriptId, $dialog, editor_id ){ + function saveScript(scriptId, $dialog, editor_id){ var $scriptIdInput = $dialog.find('.script-id-input'); - if ( !scriptId ) { + if (!scriptId) { xmodal.message('Error: Empty Script ID', 'Please give your script an ID before saving.'); } // check for valid script ID - else if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(scriptId)){ + else if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(scriptId)) { xmodal.message({ title: 'Error: Invalid Characters in Script ID', message: 'Script ID must start with a letter and can only consist of letters, numbers, and the underscore "_" character.', action: function(){ setTimeout(function(){ $scriptIdInput.focus().select(); - },10); + }, 10); } }); } // check to see if script already exists - else if ( scriptEditor.scriptIds.indexOf($scriptIdInput.val()) > -1 ) { + else if (scriptEditor.scriptIds.indexOf($scriptIdInput.val()) > -1) { // TODO: (maybe) get list via REST instead of when the page loads xmodal.message({ title: 'Error: Script ID Already Exists', @@ -130,7 +135,7 @@ var XNAT = getObject(XNAT||{}); action: function(){ setTimeout(function(){ $scriptIdInput.focus().select(); - },10); + }, 10); } }); } @@ -141,31 +146,30 @@ var XNAT = getObject(XNAT||{}); $dialog.find('.editor-content').attr('id'); var data = { - scriptLabel: $dialog.find('.scriptLabel').val(), + // copy id to label if empty + scriptLabel: $dialog.find('.scriptLabel').val() || scriptId, content: ace.edit(editor_id).getSession().getValue(), description: $dialog.find('.script-description').val(), scriptVersion: $dialog.find('.script-version').val(), language: $dialog.find('input.language').val() || '' }; - if (!data.content || /^\s+$/.test(data.content)){ + if (!data.content || /^\s+$/.test(data.content)) { xmodal.message('No Content', 'Please add script content and try again.'); } else { - var csrfParam = { - XNAT_CSRF: csrfToken - }; xhr.put(scriptURL(scriptId, csrfParam), data, { success: function(){ + xmodal.close($dialog); + scriptEditor.loadScripts(); xmodal.message('Success', 'Your script was successfully saved.', { action: function(){ - scriptEditor.loadScripts(); xmodal.closeAll(); } }); //xmodal.close($dialog); }, - error: function( request, status, error ){ + error: function(request, status, error){ xmodal.message('Error', 'An error occurred: [' + status + '] ' + error); } }); @@ -174,26 +178,120 @@ var XNAT = getObject(XNAT||{}); } var counter = 0; + + // load script into the editor + function loadEditor($dialog, json){ + + json = json || {}; + + console.log(json); + + var scriptId = json.scriptId || ''; + var lang = json.language || 'groovy'; + + $dialog.find('.id').val(json.id || ''); + $dialog.find("input.language").val(lang); + + // version menu + var $versionMenu = $dialog.find('select.script-version'); + + if (scriptId) { + + // if editing existing script, show id as text + $dialog.find('.script-id-text').html(scriptId); + $dialog.find('.script-id-input').remove(); + + // and load the versions menu + xhr.getJSON({ + url: scriptVersionsURL(scriptId), + success: function(versions){ + $versionMenu.empty().append(versions.map(function(vers, i){ + return spawn('option', { + value: vers, + html: i + 1 + }); + })); + // make sure latest version is selected + $versionMenu.find('option').last().prop('selected', true); + $versionMenu.change(); + } + }); + } + else { + $versionMenu.append('<option value="1" selected>1</option>') + } + + var $wrapper = $dialog.find('.editor-wrapper'); + + function initEditor(data){ + + var timestamp = data.timestamp || '', + modifiedDate = ''; + + // update top fields + $dialog.find('.scriptId').val(data.scriptId); + $dialog.find('.scriptLabel').val(data.scriptLabel || ''); + $dialog.find('.timestamp').val(timestamp); + $dialog.find('.script-description').val(data.description || ''); + + // update date/time in foter + if (timestamp) { + modifiedDate = ('last modified: ' + (new Date(timestamp)).toString()); + $dialog.find('.modified-date').html(modifiedDate); + } + + var editor = spawn('div', { + id: 'script-' + (scriptId || (data.id || ++counter)) + '-content', + className: 'editor-content', + html: XNAT.utils.escapeXML(data.content || '') || '', + style: { + position: 'absolute', + top: 0, right: 0, bottom: 0, left: 0, + border: '1px solid #ccc' + }, + done: function(){ + var aceEditor = ace.edit(this); + aceEditor.setTheme("ace/theme/eclipse"); + aceEditor.getSession().setMode("ace/mode/" + stringLower(lang)); + } + }); + + // put the new editor div in the wrapper + $wrapper.empty().append(editor); + + // save the id to outer scope for other functions + scriptEditor.editor_id = editor.id; + + return editor; + + } + + // init the editor on load + initEditor(json); + + // init the editor again when changing versions + $versionMenu.on('change', function(){ + var selectedVersion = this.value; + xhr.getJSON({ + url: scriptURL(scriptId + '/' + selectedVersion), + success: function(json){ + initEditor(json); + } + }) + }) + + } + + // open xmodal dialog for script editing - function renderEditor( json ){ - //var fullJson = json || {}; - //json = {}; - //var largestVersion = -1; - //var arrayLength = fullJson.length; - //for (var i = 0; i < arrayLength; i++) { - // if(fullJson[i].scriptVersion>largestVersion){ - // largestVersion = fullJson[i].scriptVersion; - // json = fullJson[i]; - // } - //} + function renderEditor(json){ var scriptId = json.scriptId || ''; var lang = json.language || 'groovy'; - var time = json.timestamp || ''; var opts = {}; opts.width = 880; - opts.height = 695; + opts.height = 720; //opts.top = 100; opts.scroll = false; opts.template = $('#script-editor-template'); @@ -201,16 +299,11 @@ var XNAT = getObject(XNAT||{}); opts.title += (lang) ? ' - ' + lang : ''; opts.esc = false; // prevent closing on 'esc' opts.enter = false; // prevents modal closing on 'enter' keypress - opts.footerContent = '<span style="color:#555;">'; - if (time){ - opts.footerContent += - 'last modified: ' + (new Date(time)).toString(); - } - opts.footerContent += '</span>'; + opts.footerContent = '<span class="modified-date" style="color:#555;"></span>'; opts.buttons = { save: { label: 'Save and Close', - action: function( obj ){ + action: function(obj){ scriptId = obj.$modal.find('.scriptId').val() || obj.$modal.find('.script-id-input').val(); @@ -223,129 +316,19 @@ var XNAT = getObject(XNAT||{}); label: 'Cancel' } }; - opts.beforeShow = function( obj ){ - - var $dialog = obj.$modal; - - $dialog.find('.id').val(json.id || ''); - $dialog.find('.scriptId').val(scriptId); - $dialog.find('.scriptLabel').val(json.scriptLabel || ''); - $dialog.find('.language').val(lang); - $dialog.find('.timestamp').val(time); - $dialog.find('.script-description').val(json.description || ''); - - var currScriptVersion = 1; - if(scriptId) { - //xhr.getJSON(XNAT.url.restUrl('/data/automation/scriptVersions/' + scriptId), function (jsonVersions) { - // for(var key in jsonVersions){ - // if(key!="contains") { - // var currScript = jsonVersions[key]; - // currScriptVersion = currScript.scriptVersion; - // $('#script-version') - // .append($("<option></option>") - // .attr("value", currScriptVersion) - // .text(currScriptVersion)); - // } - // } - // $('#script-version').val(currScriptVersion); - //}); - xhr.getJSON(XNAT.url.restUrl('/data/automation/scriptVersions/' + scriptId), function (versionsList) { - var versCounter = 1; - for(var vers in versionsList){ - if(vers!="contains") { - currScriptVersion = versionsList[vers]; - $('#script-version') - .append($("<option></option>") - .attr("value", currScriptVersion) - .text(versCounter)); - versCounter = versCounter+1; - } - } - $('#script-version').val(currScriptVersion); - }); - } - else{ - $('#script-version') - .append($("<option></option>") - .attr("value", "1") - .attr("selected","selected") - .text("1")); - } - //$("div.script-version select").val(currScriptVersion); - $('#script-version').change(function(){ - var selectedVersion = $('#script-version')[0].value; - xhr.getJSON(XNAT.url.restUrl('/data/automation/scripts/' + scriptId + '/'+ selectedVersion), function (versionObj) { - json = versionObj; - var $dialog = obj.$modal; - - //$dialog.find('.id').val(json.id || ''); - $dialog.find('.scriptId').val(scriptId || ''); - $dialog.find('.scriptLabel').val(json.scriptLabel || ''); - $dialog.find('.language').val(json.language || ''); - $dialog.find('.timestamp').val(json.timestamp || ''); - $dialog.find('.script-description').val(json.description || ''); - $dialog.find('.script-version').val(selectedVersion || ''); - - var $wrapper = $dialog.find('.editor-wrapper'); - - // make sure the editor wrapper is empty - $wrapper.empty(); - - // create an entirely new editor div - var _editor = document.createElement('div'); - _editor.id = 'script-' + (scriptId || (json.id||++counter)) + '-content'; - _editor.className = 'editor-content'; - _editor.innerHTML = XNAT.utils.escapeXML(json.content) || ''; - _editor.style = 'position:absolute;top:0;right:0;bottom:0;left:0;border: 1px solid #ccc'; - - // put the new editor div in the wrapper - $wrapper.append(_editor); - - // save the id to outer scope for other functions - scriptEditor.editor_id = _editor.id; - - var aceEditor = ace.edit(_editor); - aceEditor.setTheme("ace/theme/eclipse"); - aceEditor.getSession().setMode("ace/mode/" + stringLower(lang)); - }); - - }); - - if (scriptId){ - $dialog.find('.script-id-text').html(scriptId); - $dialog.find('.script-id-input').remove(); - //$dialog.find('.script-id-input').val(scriptId); - } - - var $wrapper = $dialog.find('.editor-wrapper'); - - // make sure the editor wrapper is empty - $wrapper.empty(); - - // create an entirely new editor div - var _editor = document.createElement('div'); - _editor.id = 'script-' + (scriptId || (json.id||++counter)) + '-content'; - _editor.className = 'editor-content'; - _editor.innerHTML = XNAT.utils.escapeXML(json.content) || ''; - _editor.style = 'position:absolute;top:0;right:0;bottom:0;left:0;border: 1px solid #ccc'; - - // put the new editor div in the wrapper - $wrapper.append(_editor); - - // save the id to outer scope for other functions - scriptEditor.editor_id = _editor.id; - - var aceEditor = ace.edit(_editor); - aceEditor.setTheme("ace/theme/eclipse"); - aceEditor.getSession().setMode("ace/mode/" + stringLower(lang)); - + opts.beforeShow = function(obj){ + loadEditor(obj.$modal, json); }; opts.afterShow = function(obj){ - if (!scriptId){ + if (!scriptId) { obj.$modal.find('.script-id-input').focus().select(); + obj.$modal.find("input.language").val(lang); } + // TODO: prompt user to save if they could lose unsaved changes }; + xmodal.open(opts); + } // open dialog to choose language @@ -357,7 +340,6 @@ var XNAT = getObject(XNAT||{}); title: 'Select a Language', content: '<p>Please select a language:</p>' + '<select class="language">' + langMenuOptions(langs) + '</select>', - //top: 120, beforeShow: function(obj){ //console.log(obj.content); }, @@ -365,9 +347,9 @@ var XNAT = getObject(XNAT||{}); okClose: false, okAction: function(obj){ var $langSelect = obj.$modal.find('select.language'), - lang = $langSelect.val(); - if (lang === '!'){ - xmodal.message('Please select a language before proceeding.',{ + lang = $langSelect.val(); + if (lang === '!') { + xmodal.message('Please select a language before proceeding.', { //top: 140, width: 350, height: 150 @@ -388,7 +370,7 @@ var XNAT = getObject(XNAT||{}); // can we assume the language choices will be // up-to-date when the dialog opens? - if (scriptEditor.languages.length){ + if (scriptEditor.languages.length) { langDialog(scriptEditor.languages); } // otherwise get the runners (again?) @@ -400,14 +382,14 @@ var XNAT = getObject(XNAT||{}); } - scriptEditor.editScript = function( scriptId ){ - xhr.getJSON(scriptURL(scriptId), function( json ){ + scriptEditor.editScript = function(scriptId){ + xhr.getJSON(scriptURL(scriptId), function(json){ renderEditor(json); }); }; - scriptEditor.duplicateScript = function( scriptId ){ - xhr.getJSON(scriptURL(scriptId), function( json ){ + scriptEditor.duplicateScript = function(scriptId){ + xhr.getJSON(scriptURL(scriptId), function(json){ var data = { //scriptId: json.scriptId + '_copy', scriptLabel: json.scriptLabel, @@ -423,8 +405,8 @@ var XNAT = getObject(XNAT||{}); scriptEditor.addScript = function(){ //window.location = url; var $langMenu = $('#add-script-language'), - _lang = $langMenu.val(); - if (_lang === '!'){ + _lang = $langMenu.val(); + if (_lang === '!') { selectLanguage(); } else { @@ -434,7 +416,7 @@ var XNAT = getObject(XNAT||{}); } }; - function doDelete( scriptId ){ + function doDelete(scriptId){ var successDialog = { title: 'Success', @@ -445,21 +427,18 @@ var XNAT = getObject(XNAT||{}); xmodal.closeAll(); } }; - var csrfParam = { - XNAT_CSRF: csrfToken - }; xhr.delete(scriptURL(scriptId, csrfParam), { success: function(){ xmodal.message(successDialog); }, - error: function( request, status, error ){ + error: function(request, status, error){ xmodal.message('Error', 'An error occurred: [' + status + '] ' + error); } }); } - scriptEditor.deleteScript = function( scriptId ){ + scriptEditor.deleteScript = function(scriptId){ xmodal.confirm({ width: 450, title: 'Delete Script?', @@ -498,3 +477,4 @@ var XNAT = getObject(XNAT||{}); }); })(XNAT); + diff --git a/src/main/webapp/scripts/xnat/app/siteEventsManager.js b/src/main/webapp/scripts/xnat/app/siteEventsManager.js index 9a255e1e9ed97e2cddfd21e86e7d9fdf5358c103..8a6dd491145bb2f78d6ea56f05bcfaba9c069fe1 100644 --- a/src/main/webapp/scripts/xnat/app/siteEventsManager.js +++ b/src/main/webapp/scripts/xnat/app/siteEventsManager.js @@ -264,18 +264,24 @@ $(function(){ if (typeof filterableFields !== 'undefined') { for (var filterable in filterableFields) { if (!filterableFields.hasOwnProperty(filterable)) continue; - var filterableVals = filterableFields[filterable]; + var filterableInfo = filterableFields[filterable]; + var filterableVals = filterableInfo["filterValues"]; + var filterableRequired = filterableInfo["filterRequired"]; + var filterableDefault = filterableInfo["defaultValue"]; + if (typeof filterableRequired !== 'undefined' && !filterableRequired) { + filterableHtml = filterableHtml + '<option value=""><NONE></option>'; + } 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">'; for (var i=0; i<filterableVals.length; i++) { - if (filterableVals[i]!=='_FILTER_NOT_REQUIRED_') { - filterableHtml = filterableHtml + '<option value="' + filterableVals[i] + '">' + filterableVals[i] + '</option>'; + if (typeof filterableDefault !== 'undefined' && filterableDefault == filterableVals[i]) { + filterableHtml = filterableHtml + '<option value="' + filterableVals[i] + '" selected>' + filterableVals[i] + '</option>'; } else { - filterableHtml = filterableHtml + '<option value=""><NONE></option>'; + 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>'; + '" class="filter" style="display:none" size="15"/> <button class="customButton">Custom Value</button></div>'; } } } diff --git a/src/main/webapp/scripts/xnat/element.js b/src/main/webapp/scripts/xnat/element.js index 36da5857f3e9fc7e680ab3784fae310019478bda..f69f88b7b493373e45f1754dd91c89b62a6de40a 100644 --- a/src/main/webapp/scripts/xnat/element.js +++ b/src/main/webapp/scripts/xnat/element.js @@ -87,7 +87,8 @@ var XNAT = getObject(XNAT||{}); }; Element.p.get$ = function(i){ - return $(this.get(i)) + this.$element = this.element$ = $(this.get(i)); + return this.$element; }; // return last element in the chain diff --git a/src/main/webapp/scripts/xnat/spawner.js b/src/main/webapp/scripts/xnat/spawner.js index afae05a62ca7b230b6253734f8c7d4e64a8a327e..6088de02d8efd6427a6efefe0d5dc4ab744bc33e 100644 --- a/src/main/webapp/scripts/xnat/spawner.js +++ b/src/main/webapp/scripts/xnat/spawner.js @@ -36,13 +36,13 @@ var XNAT = getObject(XNAT); spawner.notSpawned = []; function setRoot(url){ - url = url.replace(/^([*~.]\/+)/, '/'); + url = url.replace(/^([*~.]\/*)/, '/'); return XNAT.url.rootUrl(url) } // ================================================== // MAIN FUNCTION - spawner.spawn = function _spawn(obj){ + spawner.spawn = spawner.init = function _spawn(obj){ var frag = document.createDocumentFragment(), $frag = $(frag), @@ -225,7 +225,7 @@ var XNAT = getObject(XNAT); _spawn.get = function(){ return frag; }; - + _spawn.getContents = function(){ return $frag.contents(); }; @@ -274,6 +274,47 @@ var XNAT = getObject(XNAT); // ================================================== + // given a container and spawner object, + // spawn the elements into the container + spawner.render = function(container, obj){ + return spawner.spawn(obj).render($$(container)) + }; + + + // spawn elements with only the namespace/element path, + // container/selector, and an optional AJAX config object + // XNAT.spawner.resolve('siteAdmin/adminPage').render('#page-container'); + // or assign it to a variable and render later. + // var adminPage = XNAT.spawner.resolve('siteAdmin/adminPage'); + // adminPage.render('#page-container'); + // and methods from the AJAX request will be in .get.done(), .get.fail(), etc. + spawner.resolve = function(nsPath, opts) { + + // you can pass a config object as the only argument + opts = cloneObject(firstDefined(opts, getObject(nsPath))); + + console.log(opts); + + var url = opts.url || XNAT.url.restUrl('/xapi/spawner/resolve/' + nsPath); + + var request = XNAT.xhr.getJSON(extend(true, { + url: url + }, opts)); + + function spawnRender(container){ + return request.done(function(obj){ + spawner.spawn(obj).render($$(container)) + }); + } + + return { + done: request.done, + spawn: spawnRender, + render: spawnRender + }; + + }; + spawner.testSpawn = function(){ var jsonUrl = XNAT.url.rootUrl('/page/admin/data/config/site-admin-sample-new.json'); @@ -292,4 +333,3 @@ var XNAT = getObject(XNAT); return XNAT.spawner = spawner; })); - diff --git a/src/main/webapp/scripts/xnat/ui/input.js b/src/main/webapp/scripts/xnat/ui/input.js index d96838c4d2779e1efe2de61ae732c6adb504e788..5d5137f501af7908924c642584f97e938926394f 100644 --- a/src/main/webapp/scripts/xnat/ui/input.js +++ b/src/main/webapp/scripts/xnat/ui/input.js @@ -80,11 +80,14 @@ var XNAT = getObject(XNAT); function setupType(type, className, opts){ - var config = cloneObject(opts); - // var config = getObject(opts.config || opts.element || {}); - config.addClass = className; - config.data = extend({validate: opts.validation||opts.validate}, config.data); + var config = extend(true, {}, opts, opts.element, { + data: {}, + $: { addClass: className } + }); + config.data.validate = opts.validation||opts.validate; if (!config.data.validate) delete config.data.validate; + delete config.validation; // don't pass these to the spawn() function + delete config.validate; // ^^ return input(type, config); } @@ -120,13 +123,21 @@ var XNAT = getObject(XNAT); // checkboxes are special input.checkbox = function(config){ otherTypes.push('checkbox'); - config = getObject(config); - config = config.element || config || {}; - config.onchange = function(){ - this.value = this.checked; - }; + config = extend(true, {}, config, config.element); + // config.onchange = function(){ + // this.value = this.checked || $(this).data('uncheckedValue') || ''; + // }; return setupType('checkbox', '', config); }; + + // create an input with display: block style + input.text.block = function(config){ + config = extend(true, {}, config, config.element, { + $: { addClass: 'text block' }, + style: { display: 'block' } + }); + return input.text(config); + }; // save a list of all available input types input.types = [].concat(textTypes, numberTypes, otherTypes); diff --git a/src/main/webapp/scripts/xnat/ui/panel.js b/src/main/webapp/scripts/xnat/ui/panel.js index 6ef65f211c7f13beaed985ba7155821dd3614f1d..f185ac63545be063d2f15eb0763b296be6fd4ff0 100644 --- a/src/main/webapp/scripts/xnat/ui/panel.js +++ b/src/main/webapp/scripts/xnat/ui/panel.js @@ -48,6 +48,39 @@ var XNAT = getObject(XNAT || {}); return input; } + function loadingDialog(id){ + id = id ? '#'+id : '#loading'; + // make sure xmodal script is loaded + if (xmodal && xmodal.loading) { + return { + open: function(){ + return xmodal.loading.open(id); + }, + close: function(){ + if (id === '#*') { + xmodal.loading.closeAll(); + } + else { + xmodal.loading.close(id); + } + }, + closeAll: xmodal.loading.closeAll + } + } else { + return { + open: function(){ + // Do nothing + }, + close: function(){ + // Do nothing + }, + closeAll: function(){ + // Do nothing + } + } + } + } + /** * Initialize panel. * @param [opts] {Object} Config object @@ -144,14 +177,14 @@ var XNAT = getObject(XNAT || {}); ], // TODO: use opts.element for the panel itself - _formPanel = spawn('form.xnat-form-panel.panel.panel-default', { + _formPanel = spawn('form.xnat-form-panel.panel.panel-default', extend(true, { id: toDashed(opts.id || opts.element.id || opts.name) + '-panel', name: opts.name, method: opts.method || 'POST', action: opts.action ? XNAT.url.rootUrl(opts.action) : '#!', addClass: opts.classes || '', data: opts.data - }, [ + }, opts.element), [ (hideHeader ? ['div.hidden'] : ['div.panel-heading', [ ['h3.panel-title', opts.title] @@ -178,9 +211,9 @@ var XNAT = getObject(XNAT || {}); // find all form inputs with a name attribute $$(form).find(':input').each(function(){ - var val = dataObj[this.name||this.title]; + var val = lookupObjectValue(dataObj, this.name||this.title); - if (!val) return; + //if (!val) return; if (Array.isArray(val)) { val = val.join(', '); @@ -196,9 +229,9 @@ var XNAT = getObject(XNAT || {}); } }); - if (xmodal && xmodal.loading && xmodal.loading.closeAll){ - xmodal.loading.closeAll(); - } + + loadingDialog().closeAll(); + } @@ -211,12 +244,10 @@ var XNAT = getObject(XNAT || {}); // need a form to put the data into! // and a 'load' property too if (!form || !obj.load) { - xmodal.loading.close('#load-data'); + loadingDialog().close(); return; } - //xmodal.loading.open('#load-data'); - obj.load = (obj.load+'').trim(); // if 'load' starts with '$?', '~/', or just '/' @@ -237,7 +268,7 @@ var XNAT = getObject(XNAT || {}); obj.load = (obj.load.split(lookupPrefix)[1]||'').trim().split('|')[0]; obj.prop = obj.prop || obj.load.split('|')[1] || ''; setValues(form, lookupObjectValue(window, obj.load, obj.prop)); - xmodal.loading.close('#load-data'); + loadingDialog().close(); return form; } @@ -254,7 +285,7 @@ var XNAT = getObject(XNAT || {}); console.log(e); } - xmodal.loading.close('#load-data'); + loadingDialog().close(); return form; } @@ -278,7 +309,7 @@ var XNAT = getObject(XNAT || {}); // need a url to get the data if (!ajaxUrl || !stringable(ajaxUrl)) { - xmodal.loading.close('#load-data'); + loadingDialog().close(); return form; } @@ -307,7 +338,7 @@ var XNAT = getObject(XNAT || {}); obj.ajax.complete = function(){ - xmodal.loading.close('#load-data'); + loadingDialog().close(); }; // return the ajax thing for method chaining @@ -329,7 +360,7 @@ var XNAT = getObject(XNAT || {}); // custom event for reloading data (refresh) $formPanel.on('reload-data', function(){ loadData(this, { - refresh: opts.refresh || opts.load || opts.url + load: opts.refresh || opts.load || opts.url }); }); @@ -355,6 +386,7 @@ var XNAT = getObject(XNAT || {}); $formPanel.on('submit', function(e){ e.preventDefault(); + e.stopImmediatePropagation(); var $form = $(this).removeClass('error'), errors = 0, @@ -415,11 +447,13 @@ var XNAT = getObject(XNAT || {}); function formToJSON(form){ var json = {}; $$(form).serializeArray().forEach(function(item) { - if (typeof json[item.name] == 'undefined') { - json[item.name] = item.value || ''; + if (!item.name) return; + var name = item.name.replace(/^:/,''); + if (typeof json[name] == 'undefined') { + json[name] = item.value || ''; } else { - json[item.name] = [].concat(json[item.name], item.value||[]) ; + json[name] = [].concat(json[name], item.value||[]) ; } }); return json; @@ -435,20 +469,16 @@ var XNAT = getObject(XNAT || {}); // ALWAYS reload from the server obj.refresh = opts.refresh || opts.reload || opts.url || opts.load; if (!silent){ - //xmodal.loading.close(saveLoader.$modal); XNAT.ui.banner.top(2000, 'Data saved successfully.', 'success'); loadData($form, obj); - // xmodal.message('Data saved successfully.', { - // action: function(){ - // loadData($form, obj); - // } - // }); } } }; if (/json/i.test(opts.contentType||'')){ - ajaxConfig.data = JSON.stringify(formToJSON(this)); + // ajaxConfig.data = JSON.stringify(formToJSON(this)); + // ajaxConfig.data = JSON.stringify(form2js(this, /[:\[\]]/)); + ajaxConfig.data = JSON.stringify(form2js(this, ':')); ajaxConfig.processData = false; ajaxConfig.contentType = 'application/json'; $.ajax(ajaxConfig); @@ -529,7 +559,7 @@ var XNAT = getObject(XNAT || {}); e.preventDefault(); var $forms = $(this).find('form'); - var loader = xmodal.loading.open('#multi-save'); + var loader = loadingDialog('multi-save').open(); // reset error count on new submission multiform.errors = 0; @@ -551,7 +581,7 @@ var XNAT = getObject(XNAT || {}); // multiform.errors = 0; // multiform.count = 0; - xmodal.loading.close(loader.$modal); + loader.close(); // fire the callback function after all forms are submitted error-free @@ -629,6 +659,9 @@ var XNAT = getObject(XNAT || {}); _inner.push(['div.description', opts.description||opts.body||opts.html]); } + // add element to clear floats + _inner.push(['br.clear']); + _element = spawn('div', opts.element, _inner); return { @@ -684,13 +717,13 @@ var XNAT = getObject(XNAT || {}); } }; - - panel.input = {}; - + panel.display = function(opts){ return XNAT.ui.template.panelDisplay(opts).spawned; }; + panel.input = {}; + panel.input.text = function(opts){ return XNAT.ui.template.panelInput(opts).spawned; }; @@ -698,36 +731,77 @@ var XNAT = getObject(XNAT || {}); panel.input.number = function panelInputNumber(opts){ opts = cloneObject(opts); opts.type = 'number'; + addClassName(opts, 'input-text number'); return XNAT.ui.template.panelInput(opts).spawned; }; panel.input.email = function panelInputEmail(opts){ opts = cloneObject(opts); opts.type = 'text'; - addClassName(opts, 'email'); + addClassName(opts, 'input-text email'); return XNAT.ui.template.panelInput(opts).spawned; }; panel.input.password = function panelInputPassword(opts){ opts = cloneObject(opts); opts.type = 'password'; - addClassName(opts, 'password'); + addClassName(opts, 'input-text password'); return XNAT.ui.template.panelInput(opts).spawned; }; panel.input.date = function panelInputDate(opts){ opts = cloneObject(opts); opts.type = 'date'; - addClassName(opts, 'date'); + addClassName(opts, 'input-text date'); return XNAT.ui.template.panelInput(opts).spawned; }; panel.input.checkbox = function panelInputCheckbox(opts){ opts = cloneObject(opts); opts.type = 'checkbox'; + addClassName(opts, 'checkbox'); return XNAT.ui.template.panelInput(opts).spawned; }; + panel.input.radio = function panelInputCheckbox(opts){ + opts = cloneObject(opts); + opts.type = 'radio'; + addClassName(opts, 'radio'); + return XNAT.ui.template.panelInput(opts).spawned; + }; + + + panel.input.hidden = function panelInputHidden(opts){ + opts = cloneObject(opts); + opts.type = 'hidden'; + opts.element = extend(true, { + type: 'hidden', + className: opts.className || opts.classes || '', + name: opts.name, + id: opts.id || toDashed(opts.name), + value: opts.value || '' + }, opts.element); + + addClassName(opts.element, 'hidden'); + + if (opts.validation || opts.validate) { + extend(true, opts.element, { + data: { + validate: opts.validation || opts.validate + } + }) + } + + return spawn('input', opts.element); + }; + + + // add event handlers for setting values of + // hidden inputs controlled by checkboxes + // $('body').on('change', 'input.checkbox.controller', function(){ + // $(this).next('input.hidden').val(this.checked); + // }); + panel.input.upload = function panelInputUpload(opts){ opts = cloneObject(opts); opts.id = (opts.id||randomID('upload-', false)); @@ -780,8 +854,22 @@ var XNAT = getObject(XNAT || {}); opts.html || ''; opts.element.html = lookupValue(opts.element.html); + opts.element.title = 'Double-click to open in code editor.'; - opts.element.rows = 6; + if (opts.code || opts.codeLanguage) { + opts.code = opts.code || opts.codeLanguage; + addDataObjects(opts.element, { + codeLanguage: opts.code + }); + } + + // open code editor on double-click + opts.element.ondblclick = function(){ + var panelTextarea = XNAT.app.codeEditor.init(this, { language: opts.code || 'html' }); + panelTextarea.openEditor(); + }; + + opts.element.rows = 10; var textarea = spawn('textarea', opts.element); return XNAT.ui.template.panelDisplay(opts, textarea).spawned; @@ -794,7 +882,7 @@ var XNAT = getObject(XNAT || {}); panel.select = {}; - panel.select.menu = function panelSelectSingle(opts, multi){ + panel.select.menu = function panelSelectMenu(opts, multi){ var _menu; @@ -814,17 +902,11 @@ var XNAT = getObject(XNAT || {}); opts.element.multiple = true; } - _menu = spawn('select', opts.element, [['option', 'Select']]); + // set label: false so it's not created in XNAT.ui.select.menu() + //opts.label = false; - if (opts.options){ - forOwn(opts.options, function(name, prop){ - _menu.appendChild(spawn('option', { - html: prop.html || prop.text || prop.label || prop.value || prop, - value: prop.value || name, - selected: prop.selected || (prop.value === opts.value) - })); - }); - } + // use XNAT.ui.select.menu() to normalize rendering + _menu = XNAT.ui.select.menu(extend({}, opts, { label: false })).element; return XNAT.ui.template.panelInput(opts, _menu).spawned; @@ -836,31 +918,33 @@ var XNAT = getObject(XNAT || {}); return panel.select.menu(opts, true) }; panel.select.multi.init = panel.select.multi; + panel.select.multiple = panel.select.multi; ////////////////////////////////////////////////// // DATA PANELS - RETRIEVE/DISPLAY DATA ////////////////////////////////////////////////// + + panel.dataTable = function(opts){ - panel.data = {}; - - panel.data.table = function(opts){ + opts = cloneObject(opts); // initialize the table var dataTable = XNAT.table.dataTable(opts.data||[], opts); - return { - element: dataTable.table, - spawned: dataTable.table, - get: function(){ - return dataTable.table - } - }; + var panelElement = panel.element({ + label: opts.label, + description: opts.description + }); + + panelElement.target.appendChild(dataTable.table); + + return panelElement; }; - panel.data.list = function(opts){ + panel.dataList = function(opts){ // initialize the table opts = cloneObject(opts); opts.element = opts.element || {}; @@ -1006,28 +1090,6 @@ var XNAT = getObject(XNAT || {}); }; - function footerButton(text, type, disabled, classes){ - var button = { - type: type || 'button', - html: text || 'Submit' - }; - button.classes = [classes || '', 'btn btn-sm']; - if (type === 'link') { - button.classes.push('btn-link') - } - else if (/submit|primary/.test(type)) { - button.classes.push('btn-primary') - } - else { - button.classes.push('btn-default') - } - if (disabled) { - button.classes.push('disabled'); - button.disabled = 'disabled' - } - return spawn('button', button); - } - // return a single 'toggle' element function radioToggle(item){ @@ -1269,3 +1331,4 @@ var XNAT = getObject(XNAT || {}); }); })(XNAT, jQuery, window); + diff --git a/src/main/webapp/scripts/xnat/ui/select.js b/src/main/webapp/scripts/xnat/ui/select.js index f7467be0e2df358e4b320307c4226c10175fe357..a2fafb71f52dbeb5d3cb82464d5f3b51524d0714 100644 --- a/src/main/webapp/scripts/xnat/ui/select.js +++ b/src/main/webapp/scripts/xnat/ui/select.js @@ -26,10 +26,11 @@ var XNAT = getObject(XNAT); select = getObject(XNAT.ui.select || {}); function addOption(el, opt){ - el.appendChild(spawn('option', { + el.appendChild(spawn('option', extend(true, { value: opt.value || '', - html: opt.html || opt.text || opt.value - })); + html: opt.html || opt.text || opt.label || opt.value, + selected: opt.selected || false + }, opt.element ))); } // generate JUST the options @@ -40,6 +41,7 @@ var XNAT = getObject(XNAT); }); }; + // ======================================== // MAIN FUNCTION select.menu = function(config){ @@ -47,16 +49,19 @@ var XNAT = getObject(XNAT); var frag = document.createDocumentFragment(), menu, label; - config = getObject(config); + config = cloneObject(config); // show the label on the left by default - config.layout = config.layout || 'left'; + config.layout = firstDefined(config.layout, 'left'); - config.id = config.id || config.name || randomID('menu-', false); + config.id = config.id || toDashed(config.name) || randomID('menu-', false); + config.name = config.name || toCamelCase(config.id) || ''; config.element = extend(true, { id: config.id, - name: config.name || config.id + name: config.name, + value: config.value || '', + title: config.title || config.name || config.id || '' }, config.element); menu = spawn('select', config.element); @@ -70,20 +75,23 @@ var XNAT = getObject(XNAT); }) } else { - forOwn(config.options, function(txt, val){ + forOwn(config.options, function(val, txt){ var opt = {}; - if (stringable(val)) { + if (stringable(txt)) { opt.value = val; opt.html = txt; } else { - opt = val; + opt = txt; } addOption(menu, opt); }); } } + // force menu change event to select 'selected' option + $(menu).change(); + // if there's no label, wrap the // <select> inside a <label> element if (!config.label) { @@ -121,11 +129,18 @@ var XNAT = getObject(XNAT); select.multiple = function(opts){ - opts = getObject(opts); + opts = cloneObject(opts); opts.element = opts.element || {}; opts.element.multiple = true; return select.menu(opts); }; + + + // load data and add it to a container + //select.loadMenu = function(opts){ + // + //}; + // this script has loaded select.loaded = true; diff --git a/src/main/webapp/scripts/xnat/ui/table.js b/src/main/webapp/scripts/xnat/ui/table.js index ec651f9445b3086c73c0b42e8f414aa715d4f0b4..9bf87dc3acf445153b0d78a44e5dc8a719eafaad 100755 --- a/src/main/webapp/scripts/xnat/ui/table.js +++ b/src/main/webapp/scripts/xnat/ui/table.js @@ -51,30 +51,36 @@ var XNAT = getObject(XNAT); */ function Table(opts, config){ - this.opts = opts || {}; - this.config = config || null; + this.newTable = function(o, c){ + o = o || opts || {}; + c = c || config; + this.opts = cloneObject(o); + this.config = c ? cloneObject(c) : null; + this.table = element('table', this.opts); + this.$table = this.table$ = $(this.table); - this.table = element('table', this.opts); - this.table$ = $(this.table); + this.last = {}; - this.last = {}; + // 'parent' gets reset on return of chained methods + this.last.parent = this.table; - // 'parent' gets reset on return of chained methods - this.last.parent = this.table; + // get 'last' item wrapped in jQuery + this.last$ = function(el){ + return $(this.last[el || 'parent']); + }; - // get 'last' item wrapped in jQuery - this.last$ = function(el){ - return $(this.last[el || 'parent']); - }; + this.setLast = function(el){ + this.last.parent = + this.last[el.tagName.toLowerCase()] = + el; + }; + + this._rows = []; + this._cols = 0; // how many columns? - this.setLast = function(el){ - this.last.parent = - this.last[el.tagName.toLowerCase()] = - el; }; - this._rows = []; - this._cols = 0; // how many columns? + this.newTable(); } @@ -117,6 +123,7 @@ var XNAT = getObject(XNAT); var th = element('th', opts, content); this.last.th = th; this.last.tr.appendChild(th); + this._cols++; // do this here? return this; }; @@ -126,8 +133,9 @@ var XNAT = getObject(XNAT); //data = data || this.data || null; if (data) { this.last.tr = tr; - [].concat(data).forEach(function(item){ - _this.td(item); + [].concat(data).forEach(function(item, i){ + //if (_this._cols && _this._cols > i) return; + _this.td(item); }); } // only add <tr> elements to <table>, <thead>, <tbody>, and <tfoot> @@ -141,22 +149,23 @@ var XNAT = getObject(XNAT); return this; }; - // create a row with <tr> and <td> elements + // create a <tr> with optional <td> elements // in the <tbody> - Table.p.row = function(data, opts){ - // var tr = element('tr', opts); + Table.p.row = Table.p.addRow = function(data, opts){ data = data || []; this.tr(opts, data); - // (this.last.tbody || this.table).appendChild(tr); - // nullify last <th> and <td> elements since this is a new row - // this.last.th = this.last.td = null; + return this; + }; + + // add a <tr> to <tbody> + Table.p.bodyRow = function(data, opts){ + this.toBody().row(data, opts); return this; }; // create *multiple* <td> elements Table.p.tds = function(items, opts){ var _this = this; - // var last_tr = this.last.tr; [].concat(items).forEach(function(item){ if (stringable(item)) { _this.td(opts, item); @@ -206,34 +215,16 @@ var XNAT = getObject(XNAT); }; // reset last.parent to <thead> - Table.p.toHead = Table.p.closestBody = function(){ + Table.p.toHead = Table.p.closestHead = function(){ this.setLast(this.last.thead || this.table); return this; }; - Table.p.bodyRow = function(){ - this.toBody(); - this.tr(); - return this; - }; - - // add a SINGLE row of data - Table.p.addRow = function(data){ - var _this = this; - this.tr(); - [].concat(data).forEach(function(item){ - // could be an array of arrays - _this.td(item); - }); - return this; - }; - // add multiple rows of data? - Table.p.appendBody = function(data){ + Table.p.appendBody = Table.p.appendToBody = function(data){ var _this = this; [].concat(data).forEach(function(row){ - _this.toBody(); - _this.addRow(row); + _this.toBody().addRow(row); }); return this; }; @@ -242,11 +233,11 @@ var XNAT = getObject(XNAT); return this.table; }; - Table.p.get$ = function(){ + Table.p.$get = Table.p.get$ = function(){ return $(this.table); }; - Table.p.getHTML = function(){ + Table.p.getHTML = Table.p.html = function(){ return this.table.outerHTML; }; @@ -262,9 +253,13 @@ var XNAT = getObject(XNAT); header, cols = 0; - // don't init twice + // don't init twice? if (this.inited) { - return this + // run .init() again to + // empty table and load new data + this.table$.empty(); + //this.newTable(); + //return this } data = data || []; @@ -298,8 +293,7 @@ var XNAT = getObject(XNAT); // add the header if (header) { - this.thead(); - this.tr(); + this.thead().tr(); [].concat(header).forEach(function(item){ _this.th(item); }); @@ -335,18 +329,14 @@ var XNAT = getObject(XNAT); } return this.table; }; - - Table.p.html = function(){ - return this.table.outerHTML; - }; - + // 'opts' are options for the <table> element // 'config' is for other configurable stuff table = function(opts, config){ return new Table(opts, config); }; - // helper for future XNAT DataTable widget + // basic XNAT.dataTable widget table.dataTable = function(data, opts){ var tableData = data; @@ -428,8 +418,14 @@ var XNAT = getObject(XNAT); url: XNAT.url.rootUrl(opts.load||opts.url), dataType: opts.dataType || 'json', success: function(json){ - // handle data returned in ResultSet.Result array - json = (json.ResultSet && json.ResultSet.Result) ? json.ResultSet.Result : json; + // support custom path for returned data + if (opts.path) { + json = lookupObjectValue(json, opts.path); + } + else { + // handle data returned in ResultSet.Result array + json = (json.ResultSet && json.ResultSet.Result) ? json.ResultSet.Result : json; + } createTable(json); } }); @@ -443,6 +439,12 @@ var XNAT = getObject(XNAT); $$(opts.container).append(newTable.table); } + // add properties for Spawner compatibility + newTable.element = newTable.spawned = newTable.table; + newTable.get = function(){ + return newTable.table; + }; + return newTable; }; diff --git a/src/main/webapp/scripts/xnat/ui/tabs.js b/src/main/webapp/scripts/xnat/ui/tabs.js index 73eaf37aa06abe936f9f260c154a195e415d0bba..0930a6de21ed253e8c3382bc0106b95fa0cc3f0e 100755 --- a/src/main/webapp/scripts/xnat/ui/tabs.js +++ b/src/main/webapp/scripts/xnat/ui/tabs.js @@ -32,6 +32,9 @@ var XNAT = getObject(XNAT || {}); getObject(XNAT.page || {}); + // by default there are no groups + tabs.hasGroups = false; + // ================================================== // SET UP ONE TAB GROUP // add a single tab group to the groups @@ -81,7 +84,7 @@ var XNAT = getObject(XNAT || {}); var $group, groupId, tabId, tabIdHash, _flipper, _pane; obj = cloneObject(obj); - obj.config = cloneObject(obj.config); + obj.element = getObject(obj.element || obj.config); tabId = toDashed(obj.id || obj.name || randomID('t', false)); @@ -108,13 +111,13 @@ var XNAT = getObject(XNAT || {}); } tab.paneFooter = paneFooter; - obj.config.data = - extend(true, {}, obj.config.data, { + obj.element.data = + extend(true, {}, obj.element.data, { name: obj.name||'', tab: tabId }); - _pane = spawn('div.tab-pane', obj.config); + _pane = spawn('div.tab-pane', obj.element); // if 'active' is explicitly set, use the tabId value obj.active = (obj.active) ? tabId : ''; @@ -130,16 +133,18 @@ var XNAT = getObject(XNAT || {}); tabs.active = tab.active = tabId; } - groupId = toDashed(obj.group||'other'); - - // un-hide the group that this tab is in - // (groups are hidden until there is a tab for them) - $group = $('#' + groupId + '.tab-group'); + if (tabs.hasGroups) { + groupId = toDashed(obj.group||'other'); + // un-hide the group that this tab is in + // (groups are hidden until there is a tab for them) + $group = $$(tabs.navTabs).find('#' + groupId + '.tab-group'); + } + else { + $group = $$(tabs.navTabs).find('ul.tab-group').first(); + } - $group.show(); - // add all the flippers - $group.append(_flipper); + $group.append(_flipper).show(); function onRender(){ console.log('tab: ' + tabId) @@ -178,6 +183,11 @@ var XNAT = getObject(XNAT || {}); navTabs = spawn('div.xnat-nav-tabs'); tabContent = spawn('div.xnat-tab-content'); + // copy values to XNAT.tabs object for use elsewhere + tabs.container = container; + tabs.layout = layout; + tabs.navTabs = navTabs; + if (layout === 'left') { navTabs.className += ' side pull-left'; tabContent.className += ' side pull-right'; @@ -188,8 +198,15 @@ var XNAT = getObject(XNAT || {}); $container.append(navTabs); $container.append(tabContent); - // set up the group elements - $(navTabs).append(tab.groups(obj.meta.tabGroups)); + // set up the group elements, if present + if (obj.meta && obj.meta.tabGroups){ + tabs.hasGroups = true; + $(navTabs).append(tab.groups(obj.meta.tabGroups)); + } + else { + tabs.hasGroups = false; + $(navTabs).spawn('ul.tab-group'); + } // bind tab click events $container.on('click', 'li.tab', function(e){ diff --git a/src/main/webapp/scripts/xnat/ui/templates.js b/src/main/webapp/scripts/xnat/ui/templates.js index d5dfe3aaffa1a1e5406b12d45f54f85246d699e2..0239e1e19f3cac2a79924129fad0f4263c2056bb 100644 --- a/src/main/webapp/scripts/xnat/ui/templates.js +++ b/src/main/webapp/scripts/xnat/ui/templates.js @@ -102,7 +102,7 @@ var XNAT = getObject(XNAT); _templ = [ 'div|data-name='+(opts.name||''), { className: opts.className }, - content + [].concat(content, spawn('br.clear')) ]; _spawn = function(){ return spawn.apply(null, _templ); @@ -132,21 +132,26 @@ var XNAT = getObject(XNAT); // pass in an element or create a new 'div' element element = - element || spawn('div', { + element || spawn('div', extend(true, { id: opts.id, className: opts.className||'', title: opts.title||opts.name||opts.id, html: opts.value||opts.html||opts.text||opts.body||'' - }); + }, opts.element)); return template.panelElement(opts, [ ['label.element-label|for='+element.id||opts.id, opts.label], - ['div.element-wrapper', [ + ['div.element-wrapper', [].concat( + (opts.beforeElement ? opts.beforeElement : []), + element , - - ['div.description', opts.description] - ]] + + (opts.afterElement ? opts.afterElement : []), + + spawn('div.description', opts.description||'') + + )] ]); }; // ======================================== @@ -233,7 +238,20 @@ var XNAT = getObject(XNAT); $element.not('textarea').dataAttr('value', element.value); } - var inner = [element]; + var inner = []; + + // add 'before' content before the core element + if (opts.beforeElement) { + opts.beforeElement = stringable(opts.beforeElement) ? [opts.beforeElement] : + inner.push(spawn('span.before', opts.beforeElement)); + } + + inner.push(element); + + // add 'after' content after the core element + if (opts.afterElement) { + inner.push(spawn('span.after', opts.afterElement)); + } var hiddenInput; @@ -246,12 +264,12 @@ var XNAT = getObject(XNAT); hiddenInput = spawn('input', { type: 'hidden', name: element.name, - value: element.checked + value: element.checked ? element.value || opts.value || element.checked : false }); // change the value of the hidden input onclick element.onclick = function(){ - element.value = hiddenInput.value = this.checked.toString(); + hiddenInput.value = this.checked ? this.value || this.checked.toString() : false; }; // copy name to title @@ -262,6 +280,7 @@ var XNAT = getObject(XNAT); // add a class for easy selection addClassName(element, 'controller'); + addClassName(opts, 'controller'); // add the hidden input inner.push(hiddenInput); @@ -269,7 +288,7 @@ var XNAT = getObject(XNAT); } // add the description after the input - inner.push(['div.description', opts.description||opts.body||opts.html]); + inner.push(spawn('div.description', opts.description||opts.body||opts.html)); return template.panelElement(opts, [ ['label.element-label|for='+element.id||opts.id, opts.label], diff --git a/src/main/webapp/style/app.css b/src/main/webapp/style/app.css index 4cd19c28c30890886df1eb9b8cc5476b349a03d7..c30904d02e89af833f887b3cfeefd8719a32d99d 100644 --- a/src/main/webapp/style/app.css +++ b/src/main/webapp/style/app.css @@ -100,17 +100,17 @@ select { } cursor: default; } -.text { - font-size: 11px; - line-height: 13px; - font-family: Arial, Helvetica, sans-serif; -} +/*.text {*/ + /*font-size: 11px;*/ + /*line-height: 13px;*/ + /*font-family: Arial, Helvetica, sans-serif;*/ +/*}*/ div.link { cursor: pointer; text-decoration: none; - font-size: 11px; - font-family: Arial, Helvetica, sans-serif; + /*font-size: 11px;*/ + /*font-family: Arial, Helvetica, sans-serif;*/ color: #039; } diff --git a/src/main/webapp/xnat-templates/navigations/AppJS.vm b/src/main/webapp/xnat-templates/navigations/AppJS.vm index d6d3c39f815b1deaff419eb2f0b01a3c20da5e0e..daf24ada12d26cb2314f6107475b06a53ee19b27 100644 --- a/src/main/webapp/xnat-templates/navigations/AppJS.vm +++ b/src/main/webapp/xnat-templates/navigations/AppJS.vm @@ -25,6 +25,7 @@ <script src="${SITE_ROOT}/scripts/xnat/ui/table.js"></script> <script src="${SITE_ROOT}/scripts/xnat/ui/panel.js"></script> <script src="${SITE_ROOT}/scripts/xnat/ui/tabs.js"></script> + <script src="${SITE_ROOT}/scripts/xnat/ui/banner.js"></script> <script src="${SITE_ROOT}/scripts/xnat/ui/popup.js"></script> <script src="${SITE_ROOT}/scripts/xnat/ui/dialog.js"></script> diff --git a/src/main/webapp/xnat-templates/navigations/BaseJS.vm b/src/main/webapp/xnat-templates/navigations/BaseJS.vm index bcfbea568938050d514bd8fb3c65333dbf9d7706..b5991b58316c29d1b2a24ee4f61ef2d44856ec03 100644 --- a/src/main/webapp/xnat-templates/navigations/BaseJS.vm +++ b/src/main/webapp/xnat-templates/navigations/BaseJS.vm @@ -44,8 +44,9 @@ <script src="${SITE_ROOT}/scripts/lib/spawn/spawn.js"></script> <script src="${SITE_ROOT}/scripts/lib/js.cookie.js"></script> <script src="${SITE_ROOT}/scripts/lib/yamljs/dist/yaml.js"></script> + <script src="${SITE_ROOT}/scripts/lib/form2js/src/form2js.js"></script> - <!-- XNAT utility functions --> +<!-- XNAT utility functions --> <script src="$content.getURI('scripts/utils.js')"></script> #set ($baseJS = true) diff --git a/src/main/webapp/xnat-templates/navigations/DefaultTop.vm b/src/main/webapp/xnat-templates/navigations/DefaultTop.vm index 805854eaebbf0fc3fc685ea3cf10fe4b84980fd3..33a30d782ecf756c89d9f145eaca9cc1e42fde96 100644 --- a/src/main/webapp/xnat-templates/navigations/DefaultTop.vm +++ b/src/main/webapp/xnat-templates/navigations/DefaultTop.vm @@ -47,29 +47,26 @@ </div> </div><!-- /user_bar --> -#if ($sessionCount > 1 || $sessionIpCount > 1 ) -##If you want fewer warnings, you can eliminate $sessionCount > 1 so it will not display a warning for multiple sessions on the same IP, or increase it to $sessionCount > X where X is the maximum number of sessions you can have on the same IP before you get a warning. <script type="text/javascript"> jq(document).ready(function(){ - if (Cookies.get('WARNING_BAR') == 'CLOSED'){ jq('#attention_icon').show(); } else { jq('#warning_bar').show(); } - jq('#attention_icon').click(function(){ jq('#warning_bar').slideToggle(200); }); - jq('#warning_bar .close').click(function(){ Cookies.set('WARNING_BAR','CLOSED',{path:'/'}); jq('#warning_bar').slideUp(200); }); - }) </script> + +#if ($sessionCount > 1 || $sessionIpCount > 1 ) +##If you want fewer warnings, you can eliminate $sessionCount > 1 so it will not display a warning for multiple sessions on the same IP, or increase it to $sessionCount > X where X is the maximum number of sessions you can have on the same IP before you get a warning. <div id="warning_bar" style="display:none;"> <span class="close"><img src="$content.getURI('images/close.gif')"></span> <span> @@ -94,6 +91,20 @@ </div> #end +#if ($siteConfig.pathErrorWarning != "") + <div id="warning_bar" style="display:none;"> + <span class="close"><img src="$content.getURI('images/close.gif')"></span> + <span> + XNAT System Path Verification Failure: Contact your system administrator + <span class="tip_text">(<i>what does this mean?</i>) + <span class="tip shadowed"> + $siteConfig.pathErrorWarning + </span> + </span> + </span> + </div> +#end + <div id="main_nav"> <div class="inner"> diff --git a/src/main/webapp/xnat-templates/screens/Scripts.vm b/src/main/webapp/xnat-templates/screens/Scripts.vm index decd810e2b0468c9452ee84ca960a93a9f6983d4..318fb4d53c488b40d265bfad0b0565a47df5176c 100644 --- a/src/main/webapp/xnat-templates/screens/Scripts.vm +++ b/src/main/webapp/xnat-templates/screens/Scripts.vm @@ -208,6 +208,7 @@ <td class="edit" data-action="editScript"> <a href="#!" class="edit"><b>__SCRIPT_ID__</b></a> </td> + <td class="edit" data-action="editScript">__SCRIPT_LABEL__</td> <td class="edit" data-action="editScript">__SCRIPT_DESCRIPTION__</td> <td class="actions" style="text-align:center;white-space:nowrap;"> <a href="#!" data-action="editScript" title="edit existing script">edit</a> @@ -298,14 +299,17 @@ <p id="no-scripts-installed" style="display:none;">No scripts are currently installed on this system.</p> - <table id="scripts-table" class="xnat-table" style="display:none;width:100%;"> + <table id="scripts-table" class="sortable xnat-table" style="display:none;width:100%;"> <thead> - <th>Script ID</th> + <tr> + <th class="sort index">Script ID</th> + <th class="sort">Script Label</th> <th width="50%">Description</th> ##<th>Version</th> <th> </th> ## <th> </th> ## <th> </th> + </tr> </thead> <tbody> @@ -412,7 +416,6 @@ </div> - <!-- SCRIPT EDITOR TEMPLATE --> <div id="script-editor-template" class="html-template"> <input type="hidden" name="id" class="id" value=""> @@ -440,21 +443,24 @@ </tr> <tr> <td><b>Script Version: </b> </td> - <td><select id="script-version" class="script-version"> - <option value="!">Select a Version</option> - </select></td> + <td> + <select id="script-version" class="script-version"> +## <option value="!">Select a Version</option> + </select> + </td> </tr> </table> <br> - <div class="editor-wrapper" style="width:840px;height:482px;position:relative;"> + <div class="editor-wrapper" style="width:840px;height:440px;position:relative;"> <!-- the '.editor-content' div gets replaced when the script content is loaded --> <div class="editor-content" style="position:absolute;top:0;right:0;bottom:0;left:0;border:1px solid #ccc;"></div> </div> </div><!-- /#script-editor-template --> + <script type="text/javascript" src="$content.getURI("scripts/lib/ace/ace.js")"></script> <script type="text/javascript" src="$content.getURI("scripts/xnat/app/automation.js")"></script> - <script type="text/javascript" src="$content.getURI("scripts/xnat/app/scriptEditor.js")"></script> + <script type="text/javascript" src="$content.getURI("scripts/xnat/app/scriptEditor.js")"></script> #else <div id="congratsContainer" class="container" style="width:98%;margin:5px;background-color:#797979;"> @@ -466,3 +472,4 @@ #end <!-- END plugin-resources/webapp/xnat-templates/screens/Scripts.vm --> +