From 51912b020d1c824111ecb2bcf20d3c5d0f36a31c Mon Sep 17 00:00:00 2001 From: Luke Sikina Date: Sat, 7 Oct 2023 10:30:15 -0400 Subject: [PATCH] [ALS-5056] Get Query Site of Origin - Requirement: in order to know where to send data, we need to associate each query with the requester's site - Create table of site code, site name, site email domain - When an institutional query request is made: - Parse the site code from the email of the requesting user - Add that site code to the query request - When persisting a query object after starting a query request - Check for a site code in the query request - Add it to the query meta if it exists - Add some extra niceness for later data transfer work - S3 status code --- .../data/entity/DataSharingStatus.java | 5 ++ .../dbmi/avillach/data/entity/Site.java | 53 +++++++++++++++++++ .../data/repository/SiteRepository.java | 14 +++++ .../resources/db/sql/V6__ADD_SITE_TABLE.sql | 11 ++++ .../edu/harvard/dbmi/avillach/PicsureRS.java | 18 ++++--- .../avillach/service/PicsureQueryService.java | 15 +++++- .../avillach/service/SiteParsingService.java | 41 ++++++++++++++ .../main/resources/META-INF/persistence.xml | 1 + .../passthru/PassThroughResourceRS.java | 3 ++ .../dbmi/avillach/domain/QueryRequest.java | 11 ++++ 10 files changed, 163 insertions(+), 9 deletions(-) create mode 100644 pic-sure-api-data/src/main/java/edu/harvard/dbmi/avillach/data/entity/DataSharingStatus.java create mode 100644 pic-sure-api-data/src/main/java/edu/harvard/dbmi/avillach/data/entity/Site.java create mode 100644 pic-sure-api-data/src/main/java/edu/harvard/dbmi/avillach/data/repository/SiteRepository.java create mode 100644 pic-sure-api-data/src/main/resources/db/sql/V6__ADD_SITE_TABLE.sql create mode 100644 pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/service/SiteParsingService.java diff --git a/pic-sure-api-data/src/main/java/edu/harvard/dbmi/avillach/data/entity/DataSharingStatus.java b/pic-sure-api-data/src/main/java/edu/harvard/dbmi/avillach/data/entity/DataSharingStatus.java new file mode 100644 index 00000000..97d08e27 --- /dev/null +++ b/pic-sure-api-data/src/main/java/edu/harvard/dbmi/avillach/data/entity/DataSharingStatus.java @@ -0,0 +1,5 @@ +package edu.harvard.dbmi.avillach.data.entity; + +public enum DataSharingStatus { + Unknown, Pending, Error, Complete, NotShared +} diff --git a/pic-sure-api-data/src/main/java/edu/harvard/dbmi/avillach/data/entity/Site.java b/pic-sure-api-data/src/main/java/edu/harvard/dbmi/avillach/data/entity/Site.java new file mode 100644 index 00000000..644863cf --- /dev/null +++ b/pic-sure-api-data/src/main/java/edu/harvard/dbmi/avillach/data/entity/Site.java @@ -0,0 +1,53 @@ +package edu.harvard.dbmi.avillach.data.entity; + +import io.swagger.v3.oas.annotations.media.Schema; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +@Schema(description = "A site that contains a PIC-SURE installation that we can send data to") +@Table(uniqueConstraints = { + @UniqueConstraint(name = "unique_code", columnNames = { "code" }), + @UniqueConstraint(name = "unique_email", columnNames = { "domain" }) +}) +@Entity(name = "site") +public class Site extends BaseEntity { + + @Schema(description = "The site code. Ex: BCH") + @Column(length = 15) + private String code; + + @Schema(description = "The site name. Ex: Boston Children's") + @Column(length = 255) + private String name; + + @Schema(description = "The email domain of users for this site. Ex: childrens.harvard.edu") + @Column(length = 255) + private String domain; + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDomain() { + return domain; + } + + public void setDomain(String domain) { + this.domain = domain; + } +} diff --git a/pic-sure-api-data/src/main/java/edu/harvard/dbmi/avillach/data/repository/SiteRepository.java b/pic-sure-api-data/src/main/java/edu/harvard/dbmi/avillach/data/repository/SiteRepository.java new file mode 100644 index 00000000..b58e1a0d --- /dev/null +++ b/pic-sure-api-data/src/main/java/edu/harvard/dbmi/avillach/data/repository/SiteRepository.java @@ -0,0 +1,14 @@ +package edu.harvard.dbmi.avillach.data.repository; + +import edu.harvard.dbmi.avillach.data.entity.NamedDataset; +import edu.harvard.dbmi.avillach.data.entity.Site; + +import javax.enterprise.context.ApplicationScoped; +import javax.transaction.Transactional; +import java.util.UUID; + +@Transactional +@ApplicationScoped +public class SiteRepository extends BaseRepository{ + protected SiteRepository() {super(Site.class);} +} diff --git a/pic-sure-api-data/src/main/resources/db/sql/V6__ADD_SITE_TABLE.sql b/pic-sure-api-data/src/main/resources/db/sql/V6__ADD_SITE_TABLE.sql new file mode 100644 index 00000000..4eb9f928 --- /dev/null +++ b/pic-sure-api-data/src/main/resources/db/sql/V6__ADD_SITE_TABLE.sql @@ -0,0 +1,11 @@ +USE `picsure`; + +CREATE TABLE `site` ( + `uuid` binary(16) NOT NULL, + `code` varchar(15) COLLATE utf8_bin DEFAULT NULL, + `name` varchar(255) COLLATE utf8_bin DEFAULT NULL, + `domain` varchar(255) COLLATE utf8_bin DEFAULT NULL, + PRIMARY KEY (`uuid`), + CONSTRAINT `unique_code` UNIQUE (`code`), + CONSTRAINT `unique_domain` UNIQUE (`domain`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; diff --git a/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/PicsureRS.java b/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/PicsureRS.java index 55e4f186..0bb2db3f 100644 --- a/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/PicsureRS.java +++ b/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/PicsureRS.java @@ -8,12 +8,10 @@ import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; +import javax.ws.rs.core.SecurityContext; import edu.harvard.dbmi.avillach.domain.*; -import edu.harvard.dbmi.avillach.service.FormatService; -import edu.harvard.dbmi.avillach.service.PicsureInfoService; -import edu.harvard.dbmi.avillach.service.PicsureQueryService; -import edu.harvard.dbmi.avillach.service.PicsureSearchService; +import edu.harvard.dbmi.avillach.service.*; import io.swagger.v3.oas.annotations.OpenAPIDefinition; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.info.Info; @@ -153,11 +151,15 @@ public QueryStatus query( @Parameter @QueryParam("isInstitute") - Boolean isInstitutionQuery + Boolean isInstitutionQuery, + + @Context SecurityContext context ) { - return isInstitutionQuery == null || !isInstitutionQuery ? - queryService.query(dataQueryRequest, headers) : - queryService.institutionalQuery(dataQueryRequest, headers); + if (isInstitutionQuery == null || !isInstitutionQuery) { + return queryService.query(dataQueryRequest, headers); + } else { + return queryService.institutionalQuery(dataQueryRequest, headers, context.getUserPrincipal().getName()); + } } @POST diff --git a/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/service/PicsureQueryService.java b/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/service/PicsureQueryService.java index 6d5af78c..96dd3151 100644 --- a/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/service/PicsureQueryService.java +++ b/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/service/PicsureQueryService.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import edu.harvard.dbmi.avillach.data.entity.DataSharingStatus; import edu.harvard.dbmi.avillach.data.entity.Query; import edu.harvard.dbmi.avillach.data.entity.Resource; import edu.harvard.dbmi.avillach.data.repository.QueryRepository; @@ -12,6 +13,7 @@ import edu.harvard.dbmi.avillach.util.Utilities; import edu.harvard.dbmi.avillach.util.exception.ApplicationException; import edu.harvard.dbmi.avillach.util.exception.ProtocolException; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,6 +49,9 @@ public class PicsureQueryService { @Inject ResourceWebClient resourceWebClient; + @Inject + SiteParsingService siteParsingService; + /** * Executes a query on a PIC-SURE resource and creates a Query entity in the * database for the query. @@ -277,7 +282,9 @@ public QueryStatus queryMetadata(UUID queryId, HttpHeaders headers){ * and resource specific query (could be a string or a json object) * @return {@link QueryStatus} */ - public QueryStatus institutionalQuery(QueryRequest dataQueryRequest, HttpHeaders headers) { + public QueryStatus institutionalQuery(QueryRequest dataQueryRequest, HttpHeaders headers, String email) { + String siteCode = siteParsingService.parseSiteOfOrigin(email).orElseThrow(() -> new RuntimeException("Bad email")); + dataQueryRequest.setInstitutionOfOrigin(siteCode); Resource resource = verifyQueryRequest(dataQueryRequest, headers); dataQueryRequest.getResourceCredentials().put(ResourceWebClient.BEARER_TOKEN_KEY, resource.getToken()); @@ -314,6 +321,12 @@ private Query copyQuery(QueryRequest dataQueryRequest, Resource resource, QueryS metaData.put("commonAreaUUID", dataQueryRequest.getCommonAreaUUID()); } + if (!StringUtils.isEmpty(dataQueryRequest.getInstitutionOfOrigin())) { + metaData.put("site", dataQueryRequest.getInstitutionOfOrigin()); + } + + metaData.put("sharingStatus", DataSharingStatus.Unknown); + queryEntity.setQuery(queryJson); if (!metaData.isEmpty()) { diff --git a/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/service/SiteParsingService.java b/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/service/SiteParsingService.java new file mode 100644 index 00000000..41ae2e58 --- /dev/null +++ b/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/service/SiteParsingService.java @@ -0,0 +1,41 @@ +package edu.harvard.dbmi.avillach.service; + +import edu.harvard.dbmi.avillach.data.entity.Site; +import edu.harvard.dbmi.avillach.data.repository.SiteRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import java.util.List; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SiteParsingService { + + private static final Logger LOG = LoggerFactory.getLogger(SiteParsingService.class); + + private static final Pattern emailRegex = Pattern.compile("^([^@]+)(@)(.*)$"); + + @Inject + SiteRepository repository; + + public Optional parseSiteOfOrigin(String email) { + Matcher matcher = emailRegex.matcher(email); + if (!matcher.find()) { + LOG.warn("Unable to parse domain for email: {}", email); + return Optional.empty(); + } + + List matchingDomains = repository.getByColumn("domain", matcher.group(3)); + if (matchingDomains.isEmpty()) { + LOG.warn("Unable to match domain for email: {}, looked for domain: {}", email, matcher.group(3)); + return Optional.empty(); + } + if (matchingDomains.size() > 1) { + LOG.warn("Multiple domains match email. This should never happen! Email: {}", email); + return Optional.empty(); + } + return Optional.of(matchingDomains.get(0).getCode()); + } +} diff --git a/pic-sure-api-war/src/main/resources/META-INF/persistence.xml b/pic-sure-api-war/src/main/resources/META-INF/persistence.xml index 72761fce..8e43ad74 100644 --- a/pic-sure-api-war/src/main/resources/META-INF/persistence.xml +++ b/pic-sure-api-war/src/main/resources/META-INF/persistence.xml @@ -7,6 +7,7 @@ edu.harvard.dbmi.avillach.data.entity.Query edu.harvard.dbmi.avillach.data.entity.Resource edu.harvard.dbmi.avillach.data.entity.NamedDataset + edu.harvard.dbmi.avillach.data.entity.Site diff --git a/pic-sure-resources/pic-sure-passthrough-resource/src/main/java/edu/harvard/hms/dbmi/avillach/resource/passthru/PassThroughResourceRS.java b/pic-sure-resources/pic-sure-passthrough-resource/src/main/java/edu/harvard/hms/dbmi/avillach/resource/passthru/PassThroughResourceRS.java index e251a759..fdcb93f4 100644 --- a/pic-sure-resources/pic-sure-passthrough-resource/src/main/java/edu/harvard/hms/dbmi/avillach/resource/passthru/PassThroughResourceRS.java +++ b/pic-sure-resources/pic-sure-passthrough-resource/src/main/java/edu/harvard/hms/dbmi/avillach/resource/passthru/PassThroughResourceRS.java @@ -5,8 +5,10 @@ import javax.inject.Inject; import javax.ws.rs.*; +import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; +import javax.ws.rs.core.SecurityContext; import org.apache.http.Header; import org.apache.http.HttpResponse; @@ -102,6 +104,7 @@ public QueryStatus query(QueryRequest queryRequest) { chainRequest.setResourceCredentials(queryRequest.getResourceCredentials()); chainRequest.setResourceUUID(UUID.fromString(properties.getTargetResourceId())); chainRequest.setCommonAreaUUID(queryRequest.getCommonAreaUUID()); + chainRequest.setInstitutionOfOrigin(queryRequest.getInstitutionOfOrigin()); String payload = objectMapper.writeValueAsString(chainRequest); HttpResponse response = httpClient.retrievePostResponse( diff --git a/pic-sure-resources/pic-sure-resource-api/src/main/java/edu/harvard/dbmi/avillach/domain/QueryRequest.java b/pic-sure-resources/pic-sure-resource-api/src/main/java/edu/harvard/dbmi/avillach/domain/QueryRequest.java index 5b7b7228..3a8ee942 100644 --- a/pic-sure-resources/pic-sure-resource-api/src/main/java/edu/harvard/dbmi/avillach/domain/QueryRequest.java +++ b/pic-sure-resources/pic-sure-resource-api/src/main/java/edu/harvard/dbmi/avillach/domain/QueryRequest.java @@ -56,6 +56,9 @@ public class QueryRequest { @Schema(hidden = true) private UUID commonAreaUUID; + @Schema(hidden = true) + private String institutionOfOrigin; + public Map getResourceCredentials() { return resourceCredentials; } @@ -87,4 +90,12 @@ public UUID getCommonAreaUUID() { public void setCommonAreaUUID(UUID commonAreaUUID) { this.commonAreaUUID = commonAreaUUID; } + + public String getInstitutionOfOrigin() { + return institutionOfOrigin; + } + + public void setInstitutionOfOrigin(String institutionOfOrigin) { + this.institutionOfOrigin = institutionOfOrigin; + } }