From 7417de767e54a6c162b757002ef4120931bcc84b Mon Sep 17 00:00:00 2001 From: Rick Herrick <jrherrick@wustl.edu> Date: Thu, 1 Sep 2016 17:10:57 -0500 Subject: [PATCH] XNAT-4394 Updated investigators API to include projects on which investigator is PI or associate investigator. --- .../org/nrg/xapi/rest/data/Investigator.java | 209 ++++++++++++++++++ .../xapi/rest/data/InvestigatorService.java | 48 ++++ .../nrg/xapi/rest/data/InvestigatorsApi.java | 80 ++----- 3 files changed, 279 insertions(+), 58 deletions(-) create mode 100644 src/main/java/org/nrg/xapi/rest/data/Investigator.java create mode 100644 src/main/java/org/nrg/xapi/rest/data/InvestigatorService.java diff --git a/src/main/java/org/nrg/xapi/rest/data/Investigator.java b/src/main/java/org/nrg/xapi/rest/data/Investigator.java new file mode 100644 index 00000000..1a9ca3ac --- /dev/null +++ b/src/main/java/org/nrg/xapi/rest/data/Investigator.java @@ -0,0 +1,209 @@ +package org.nrg.xapi.rest.data; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.apache.commons.lang3.NotImplementedException; +import org.nrg.xdat.bean.XnatInvestigatordataBean; +import org.nrg.xdat.model.XnatInvestigatordataI; +import org.nrg.xdat.om.XnatInvestigatordata; + +import java.io.Writer; +import java.sql.Array; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.*; + +/** + * Provides a wrapper around the {@link XnatInvestigatordata}, {@link XnatInvestigatordataI}, and {@link + * XnatInvestigatordataBean} classes. This omits extraneous properties (e.g. XFT integration attributes) and provides + * extra attributes for aggregated or synthesized properties, such as lists of projects with which the investigator is + * associated. + */ +public class Investigator implements XnatInvestigatordataI { + public Investigator(final XnatInvestigatordataI investigator, final Collection<String> primaryProjects, final Collection<String> investigatorProjects) { + _xnatInvestigatordataId = investigator.getXnatInvestigatordataId(); + _id = investigator.getId(); + _title = investigator.getTitle(); + _firstname = investigator.getFirstname(); + _lastname = investigator.getLastname(); + _institution = investigator.getInstitution(); + _department = investigator.getDepartment(); + _email = investigator.getEmail(); + _phone = investigator.getPhone(); + _primaryProjects.addAll(primaryProjects); + _investigatorProjects.addAll(investigatorProjects); + } + + public Investigator(final ResultSet resultSet) throws SQLException { + _xnatInvestigatordataId = resultSet.getInt(0); + _id = resultSet.getString(1); + _title = resultSet.getString(2); + _firstname = resultSet.getString(2); + _lastname = resultSet.getString(2); + _institution = resultSet.getString(2); + _department = resultSet.getString(2); + _email = resultSet.getString(2); + _phone = resultSet.getString(2); + _primaryProjects.addAll(getProjectIds(resultSet.getArray(10))); + _investigatorProjects.addAll(getProjectIds(resultSet.getArray(11))); + } + + @Override + @JsonIgnore + public String getXSIType() { + return XnatInvestigatordata.SCHEMA_ELEMENT_NAME; + } + + @Override + public void toXML(final Writer writer) throws Exception { + + } + + @Override + public String getTitle() { + return _title; + } + + @Override + public void setTitle(final String title) { + throw new NotImplementedException("Set methods on this class are not implemented: the class is meant to be read-only and only for reference purposes."); + } + + @Override + public String getFirstname() { + return _firstname; + } + + @Override + public void setFirstname(final String firstname) { + throw new NotImplementedException("Set methods on this class are not implemented: the class is meant to be read-only and only for reference purposes."); + } + + @Override + public String getLastname() { + return _lastname; + } + + @Override + public void setLastname(final String lastname) { + throw new NotImplementedException("Set methods on this class are not implemented: the class is meant to be read-only and only for reference purposes."); + } + + @Override + public String getInstitution() { + return _institution; + } + + @Override + public void setInstitution(final String institution) { + throw new NotImplementedException("Set methods on this class are not implemented: the class is meant to be read-only and only for reference purposes."); + } + + @Override + public String getDepartment() { + return _department; + } + + @Override + public void setDepartment(final String department) { + throw new NotImplementedException("Set methods on this class are not implemented: the class is meant to be read-only and only for reference purposes."); + } + + @Override + public String getEmail() { + return _email; + } + + @Override + public void setEmail(final String email) { + throw new NotImplementedException("Set methods on this class are not implemented: the class is meant to be read-only and only for reference purposes."); + } + + @Override + public String getPhone() { + return _phone; + } + + @Override + public void setPhone(final String phone) { + throw new NotImplementedException("Set methods on this class are not implemented: the class is meant to be read-only and only for reference purposes."); + } + + @Override + public String getId() { + return _id; + } + + @Override + public void setId(final String v) { + throw new NotImplementedException("Set methods on this class are not implemented: the class is meant to be read-only and only for reference purposes."); + } + + @Override + public Integer getXnatInvestigatordataId() { + return _xnatInvestigatordataId; + } + + /** + * Gets the list of IDs for projects on which the investigator is the primary investigator. + * + * @return The projects on which the investigator is the primary investigator. + */ + @SuppressWarnings("unused") + public Set<String> getPrimaryProjects() { + return new HashSet<>(_primaryProjects); + } + + /** + * Indicates whether the investigator is a PI on the indicated project. + * + * @param project The project to test. + * + * @return Returns true if the investigator is the PI on the indicated project, false otherwise. + */ + @SuppressWarnings("unused") + public boolean hasPrimaryProject(final String project) { + return _primaryProjects.contains(project); + } + + /** + * Gets the list of IDs for projects on which the investigator is the primary investigator. + * + * @return The projects on which the investigator is the primary investigator. + */ + @SuppressWarnings("unused") + public Set<String> getInvestigatorProjects() { + return new HashSet<>(_investigatorProjects); + } + + /** + * Indicates whether the investigator is a member of the indicated project team. Note that this does not check + * whether the investigator is the project's PI. + * + * @param project The project to test. + * + * @return Returns true if the investigator is a member of the indicated project team, false otherwise. + */ + @SuppressWarnings("unused") + public boolean hasInvestigatorProject(final String project) { + return _investigatorProjects.contains(project); + } + + private static Collection<? extends String> getProjectIds(final Array array) throws SQLException { + if (array == null) { + return Collections.emptyList(); + } + return Arrays.asList((String[]) array.getArray()); + } + + private final Integer _xnatInvestigatordataId; + private final String _id; + private final String _title; + private final String _firstname; + private final String _lastname; + private final String _institution; + private final String _department; + private final String _email; + private final String _phone; + private final Set<String> _primaryProjects = new HashSet<>(); + private final Set<String> _investigatorProjects = new HashSet<>(); +} diff --git a/src/main/java/org/nrg/xapi/rest/data/InvestigatorService.java b/src/main/java/org/nrg/xapi/rest/data/InvestigatorService.java new file mode 100644 index 00000000..ad6e1a88 --- /dev/null +++ b/src/main/java/org/nrg/xapi/rest/data/InvestigatorService.java @@ -0,0 +1,48 @@ +package org.nrg.xapi.rest.data; + +import org.nrg.xdat.model.XnatInvestigatordataI; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Service; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +/** + * Manages operations with {@link Investigator investigator proxy objects}. This is not a full-on Hibernate service, + * since the "entities" managed are not Hibernate entities but instead are composite objects that represent XFT {@link + * XnatInvestigatordataI} objects as well as metadata aggregated from other tables. + */ +@Service +public class InvestigatorService { + @Autowired + public InvestigatorService(final JdbcTemplate template) { + _template = template; + } + + public Investigator getInvestigator(final int xnatInvestigatordataId) { + return _template.queryForObject(INVESTIGATOR_QUERY + BY_ID_WHERE, new Object[]{xnatInvestigatordataId}, ROW_MAPPER); + } + + public Investigator getInvestigator(final String firstname, final String lastname) { + return _template.queryForObject(INVESTIGATOR_QUERY + BY_FIRST_LAST_WHERE, new Object[]{firstname, lastname}, ROW_MAPPER); + } + + public List<Investigator> getInvestigators() { + return _template.query(INVESTIGATOR_QUERY, ROW_MAPPER); + } + + private static final String INVESTIGATOR_QUERY = "SELECT investigator.xnat_investigatordata_id AS xnat_investigatordata_id, investigator.id AS id, investigator.title AS title, investigator.firstname AS firstname, investigator.lastname AS lastname, investigator.institution AS institution, investigator.department AS department, investigator.email AS email, investigator.phone AS phone, (SELECT array(SELECT project.id FROM xnat_projectdata project WHERE project.pi_xnat_investigatordata_id = investigator.xnat_investigatordata_id)) AS primary_inv, (SELECT array(SELECT project_inv.xnat_projectdata_id FROM xnat_projectdata_investigator project_inv WHERE project_inv.xnat_investigatordata_xnat_investigatordata_id = investigator.xnat_investigatordata_id)) AS inv FROM xnat_investigatordata investigator"; + private static final String BY_ID_WHERE = " WHERE investigator.xnat_investigatordata_id = ?"; + private static final String BY_FIRST_LAST_WHERE = " WHERE investigator.firstname = ? AND investigator.lastname = ?"; + + private static final RowMapper<Investigator> ROW_MAPPER = new RowMapper<Investigator>() { + @Override + public Investigator mapRow(final ResultSet resultSet, final int i) throws SQLException { + return new Investigator(resultSet); + } + }; + private final JdbcTemplate _template; +} diff --git a/src/main/java/org/nrg/xapi/rest/data/InvestigatorsApi.java b/src/main/java/org/nrg/xapi/rest/data/InvestigatorsApi.java index db058218..be3b56eb 100644 --- a/src/main/java/org/nrg/xapi/rest/data/InvestigatorsApi.java +++ b/src/main/java/org/nrg/xapi/rest/data/InvestigatorsApi.java @@ -6,11 +6,11 @@ import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponses; import org.apache.commons.lang3.StringUtils; import org.nrg.framework.annotations.XapiRestController; -import org.nrg.xdat.bean.XnatInvestigatordataBean; -import org.nrg.xdat.model.XnatInvestigatordataI; import org.nrg.xdat.om.XnatInvestigatordata; import org.nrg.xdat.rest.AbstractXapiRestController; +import org.nrg.xdat.security.helpers.Roles; import org.nrg.xdat.security.services.RoleHolder; +import org.nrg.xdat.security.services.RoleRepositoryServiceI; import org.nrg.xdat.security.services.UserManagementServiceI; import org.nrg.xft.XFTItem; import org.nrg.xft.event.EventUtils; @@ -22,12 +22,8 @@ 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.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; import org.springframework.web.bind.annotation.*; -import java.sql.ResultSet; -import java.sql.SQLException; import java.util.List; @Api(description = "XNAT Data Investigators API") @@ -35,42 +31,42 @@ import java.util.List; @RequestMapping(value = "/investigators") public class InvestigatorsApi extends AbstractXapiRestController { @Autowired - public InvestigatorsApi(final UserManagementServiceI userManagementService, final RoleHolder roleHolder, final JdbcTemplate jdbcTemplate) { + public InvestigatorsApi(final UserManagementServiceI userManagementService, final RoleHolder roleHolder, final InvestigatorService service) { super(userManagementService, roleHolder); - _jdbcTemplate = jdbcTemplate; + _service = service; } - @ApiOperation(value = "Get list of investigators.", notes = "The investigators function returns a list of all investigators configured in the XNAT system.", response = XnatInvestigatordataI.class, responseContainer = "List") + @ApiOperation(value = "Get list of investigators.", notes = "The investigators function returns a list of all investigators configured in the XNAT system.", response = Investigator.class, responseContainer = "List") @ApiResponses({@ApiResponse(code = 200, message = "Returns a list of all of the currently configured investigators."), @ApiResponse(code = 500, message = "An unexpected or unknown error occurred")}) @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET) @ResponseBody - public ResponseEntity<List<XnatInvestigatordataI>> getInvestigators() { - return new ResponseEntity<>(_jdbcTemplate.query(PI_QUERY + PI_ORDERBY, PI_ROWMAPPER), HttpStatus.OK); + public ResponseEntity<List<Investigator>> getInvestigators() { + return new ResponseEntity<>(_service.getInvestigators(), HttpStatus.OK); } - @ApiOperation(value = "Gets the requested investigator.", notes = "Returns the investigator with the specified ID.", response = XnatInvestigatordataI.class) + @ApiOperation(value = "Gets the requested investigator.", notes = "Returns the investigator with the specified ID.", response = Investigator.class) @ApiResponses({@ApiResponse(code = 200, message = "Returns the requested investigator."), @ApiResponse(code = 404, message = "The requested investigator wasn't found."), @ApiResponse(code = 500, message = "An unexpected or unknown error occurred.")}) @RequestMapping(value = "{investigatorId}", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET) @ResponseBody - public ResponseEntity<XnatInvestigatordataI> getInvestigator(@PathVariable("investigatorId") final int investigatorId) { - final XnatInvestigatordataI investigator = getXnatInvestigator(investigatorId); + public ResponseEntity<Investigator> getInvestigator(@PathVariable("investigatorId") final int investigatorId) { + final Investigator investigator = _service.getInvestigator(investigatorId); if (investigator == null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } return new ResponseEntity<>(investigator, HttpStatus.OK); } - @ApiOperation(value = "Creates a new investigator from the submitted attributes.", notes = "Returns the newly created investigator with the submitted attributes.", response = XnatInvestigatordataI.class) + @ApiOperation(value = "Creates a new investigator from the submitted attributes.", notes = "Returns the newly created investigator with the submitted attributes.", response = Investigator.class) @ApiResponses({@ApiResponse(code = 200, message = "Returns the newly created investigator."), @ApiResponse(code = 403, message = "Insufficient privileges to create the submitted investigator."), @ApiResponse(code = 404, message = "The requested investigator wasn't found."), @ApiResponse(code = 500, message = "An unexpected or unknown error occurred.")}) @RequestMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.POST) @ResponseBody - public ResponseEntity<XnatInvestigatordataI> createInvestigator(@RequestBody final XnatInvestigatordataBean investigator) throws Exception { + public ResponseEntity<Investigator> createInvestigator(@RequestBody final Investigator investigator) throws Exception { if (StringUtils.isBlank(investigator.getFirstname()) || StringUtils.isBlank(investigator.getLastname())) { _log.error("User {} tried to create investigator without a first or last name.", getSessionUser().getUsername()); return new ResponseEntity<>(HttpStatus.BAD_REQUEST); @@ -88,10 +84,10 @@ public class InvestigatorsApi extends AbstractXapiRestController { _log.error("Failed to create a new investigator for user {}. Check the logs for possible errors or exceptions.", user.getUsername()); return new ResponseEntity<>(HttpStatus.CONFLICT); } - return new ResponseEntity<>(getXnatInvestigator(investigator.getFirstname(), investigator.getLastname()), HttpStatus.OK); + return new ResponseEntity<>(_service.getInvestigator(investigator.getFirstname(), investigator.getLastname()), HttpStatus.OK); } - @ApiOperation(value = "Updates the requested investigator from the submitted attributes.", notes = "Returns the updated investigator.", response = XnatInvestigatordataI.class) + @ApiOperation(value = "Updates the requested investigator from the submitted attributes.", notes = "Returns the updated investigator.", response = Investigator.class) @ApiResponses({@ApiResponse(code = 200, message = "Returns the updated investigator."), @ApiResponse(code = 304, message = "The requested investigator is the same as the submitted investigator."), @ApiResponse(code = 403, message = "Insufficient privileges to edit the requested investigator."), @@ -99,8 +95,9 @@ public class InvestigatorsApi extends AbstractXapiRestController { @ApiResponse(code = 500, message = "An unexpected or unknown error occurred.")}) @RequestMapping(value = "{investigatorId}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.PUT) @ResponseBody - public ResponseEntity<XnatInvestigatordataI> updateInvestigator(@PathVariable("investigatorId") final int investigatorId, @RequestBody final XnatInvestigatordataBean investigator) throws Exception { + public ResponseEntity<Investigator> updateInvestigator(@PathVariable("investigatorId") final int investigatorId, @RequestBody final Investigator investigator) throws Exception { final UserI user = getSessionUser(); + final XnatInvestigatordata existing = XnatInvestigatordata.getXnatInvestigatordatasByXnatInvestigatordataId(investigatorId, user, false); if (existing == null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); @@ -142,7 +139,7 @@ public class InvestigatorsApi extends AbstractXapiRestController { _log.error("Failed to save the investigator with ID {}. Check the logs for possible errors or exceptions."); return new ResponseEntity<>(HttpStatus.CONFLICT); } - return new ResponseEntity<>(getXnatInvestigator(investigatorId), HttpStatus.OK); + return new ResponseEntity<>(_service.getInvestigator(investigatorId), HttpStatus.OK); } return new ResponseEntity<>(HttpStatus.NOT_MODIFIED); @@ -150,12 +147,16 @@ public class InvestigatorsApi extends AbstractXapiRestController { @ApiOperation(value = "Deletes the requested investigator.", notes = "Returns true if the requested investigator was successfully deleted. Returns false otherwise.", response = Boolean.class) @ApiResponses({@ApiResponse(code = 200, message = "Returns true to indicate the requested investigator was successfully deleted."), + @ApiResponse(code = 403, message = "The user doesn't have permission to delete investigators."), @ApiResponse(code = 404, message = "The requested investigator wasn't found."), @ApiResponse(code = 500, message = "An unexpected or unknown error occurred.")}) @RequestMapping(value = "{investigatorId}", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.DELETE) @ResponseBody public ResponseEntity<Boolean> deleteInvestigator(@PathVariable("investigatorId") final int investigatorId) throws Exception { final UserI user = getSessionUser(); + if (!Roles.isSiteAdmin(user)) { + return new ResponseEntity<>(HttpStatus.FORBIDDEN); + } final XnatInvestigatordata investigator = XnatInvestigatordata.getXnatInvestigatordatasByXnatInvestigatordataId(investigatorId, user, false); if (investigator == null) { return new ResponseEntity<>(false, HttpStatus.NOT_FOUND); @@ -164,44 +165,7 @@ public class InvestigatorsApi extends AbstractXapiRestController { return new ResponseEntity<>(true, HttpStatus.OK); } - private XnatInvestigatordataI getXnatInvestigator(final int investigatorId) { - final List<XnatInvestigatordataI> investigators = _jdbcTemplate.query(PI_QUERY + PI_BY_ID, new Object[]{investigatorId}, PI_ROWMAPPER); - if (investigators.size() == 0) { - return null; - } - return investigators.get(0); - } - - private XnatInvestigatordataI getXnatInvestigator(final String firstname, final String lastname) { - final List<XnatInvestigatordataI> investigators = _jdbcTemplate.query(PI_QUERY + PI_BY_NAME, new Object[]{firstname, lastname}, PI_ROWMAPPER); - if (investigators.size() == 0) { - return null; - } - return investigators.get(0); - } - private static final Logger _log = LoggerFactory.getLogger(InvestigatorsApi.class); - // TODO: InvestigatorListResource uses a query that adds the PI's login. XnatInvestigatordataBean has no setter for login, so leaving it off for now. - private static final String PI_QUERY = "SELECT DISTINCT ON (lastname, firstname) xnat_investigatorData_id, title, firstname, lastname, institution, department, email, phone FROM xnat_investigatorData "; - private static final String PI_ORDERBY = "ORDER BY lastname, firstname"; - private static final String PI_BY_ID = "WHERE xnat_investigatordata_id = ?"; - private static final String PI_BY_NAME = "WHERE firstname = ? AND lastname = ?"; - private final static RowMapper<XnatInvestigatordataI> PI_ROWMAPPER = new RowMapper<XnatInvestigatordataI>() { - @Override - public XnatInvestigatordataI mapRow(final ResultSet resultSet, final int i) throws SQLException { - return new XnatInvestigatordataBean() {{ - setXnatInvestigatordataId(resultSet.getInt("xnat_investigatordata_id")); - setTitle(resultSet.getString("title")); - setFirstname(resultSet.getString("firstname")); - setLastname(resultSet.getString("lastname")); - setDepartment(resultSet.getString("department")); - setInstitution(resultSet.getString("institution")); - setEmail(resultSet.getString("email")); - setPhone(resultSet.getString("phone")); - }}; - } - }; - - private final JdbcTemplate _jdbcTemplate; + private final InvestigatorService _service; } -- GitLab