From 6692b170f5bc6822ba5acb5b4fca3dced65b1957 Mon Sep 17 00:00:00 2001 From: mcmcphillips Date: Tue, 26 Mar 2024 12:15:59 -0700 Subject: [PATCH 01/14] MAT-6916 partial --- .../controller/VsacController.java | 50 +++++++----- .../madie/terminology/models/CodeSystem.java | 26 +++++++ .../madie/terminology/models/Identifier.java | 5 ++ .../cms/madie/terminology/models/Meta.java | 9 +++ .../service/FhirTerminologyService.java | 27 +++++++ .../util/TerminologyServiceUtil.java | 7 ++ .../FhirTerminologyServiceWebClient.java | 26 +++++++ .../TerminologyServiceWebClient.java | 76 +++++++++---------- .../FhirTerminologyServiceWebClientTest.java | 1 + .../TerminologyServiceWebClientTest.java | 1 - 10 files changed, 172 insertions(+), 56 deletions(-) create mode 100644 src/main/java/gov/cms/madie/terminology/models/CodeSystem.java create mode 100644 src/main/java/gov/cms/madie/terminology/models/Identifier.java create mode 100644 src/main/java/gov/cms/madie/terminology/models/Meta.java diff --git a/src/main/java/gov/cms/madie/terminology/controller/VsacController.java b/src/main/java/gov/cms/madie/terminology/controller/VsacController.java index a73fc16..3f808e8 100644 --- a/src/main/java/gov/cms/madie/terminology/controller/VsacController.java +++ b/src/main/java/gov/cms/madie/terminology/controller/VsacController.java @@ -38,19 +38,19 @@ public class VsacController { @GetMapping(path = "/valueset", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity getValueSet( - Principal principal, - @RequestParam String oid, - @RequestParam(required = false, name = "profile") String profile, - @RequestParam(required = false, name = "includeDraft") String includeDraft, - @RequestParam(required = false, name = "release") String release, - @RequestParam(required = false, name = "version") String version) { + Principal principal, + @RequestParam String oid, + @RequestParam(required = false, name = "profile") String profile, + @RequestParam(required = false, name = "includeDraft") String includeDraft, + @RequestParam(required = false, name = "release") String release, + @RequestParam(required = false, name = "version") String version) { log.debug("Entering: getValueSet()"); final String username = principal.getName(); Optional umlsUser = vsacService.findByHarpId(username); if (umlsUser.isPresent()) { RetrieveMultipleValueSetsResponse valuesetResponse = - vsacService.getValueSet(oid, umlsUser.get(), profile, includeDraft, release, version); + vsacService.getValueSet(oid, umlsUser.get(), profile, includeDraft, release, version); ValueSet fhirValueSet = vsacService.convertToFHIRValueSet(valuesetResponse); log.debug("valueset id = " + fhirValueSet.getId()); @@ -67,7 +67,7 @@ protected String serializeFhirValueset(ValueSet fhirValueSet) { @PutMapping(path = "/value-sets/searches", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity searchValueSets( - Principal principal, @RequestBody ValueSetsSearchCriteria searchCriteria) { + Principal principal, @RequestBody ValueSetsSearchCriteria searchCriteria) { log.debug("VsacController::getValueSets"); final String username = principal.getName(); @@ -75,11 +75,11 @@ public ResponseEntity searchValueSets( if (umlsUser.isPresent()) { List vsacValueSets = - vsacService.getValueSets(searchCriteria, umlsUser.get()); + vsacService.getValueSets(searchCriteria, umlsUser.get()); List fhirValueSets = vsacService.convertToFHIRValueSets(vsacValueSets); String serializedValueSets = - fhirValueSets.stream().map(this::serializeFhirValueset).collect(Collectors.joining(", ")); + fhirValueSets.stream().map(this::serializeFhirValueset).collect(Collectors.joining(", ")); return ResponseEntity.ok().body("[" + serializedValueSets + "]"); } @@ -88,14 +88,14 @@ public ResponseEntity searchValueSets( @PutMapping("/qdm/value-sets/searches") public ResponseEntity> getQdmValueSets( - Principal principal, @RequestBody ValueSetsSearchCriteria searchCriteria) { + Principal principal, @RequestBody ValueSetsSearchCriteria searchCriteria) { log.debug("VsacController::getQdmValueSets"); final String username = principal.getName(); Optional umlsUser = vsacService.findByHarpId(username); if (umlsUser.isPresent()) { List qdmValueSets = - vsacService.getValueSetsInQdmFormat(searchCriteria, umlsUser.get()); + vsacService.getValueSetsInQdmFormat(searchCriteria, umlsUser.get()); return ResponseEntity.ok().body(qdmValueSets); } @@ -104,9 +104,9 @@ public ResponseEntity> getQdmValueSets( @PutMapping(path = "/validations/codes", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity> validateCodes( - Principal principal, - @RequestBody List cqlCodes, - @RequestParam(required = false, defaultValue = "FHIR") String model) { + Principal principal, + @RequestBody List cqlCodes, + @RequestParam(required = false, defaultValue = "FHIR") String model) { final String username = principal.getName(); Optional umlsUser = vsacService.findByHarpId(username); if (umlsUser.isPresent() && umlsUser.get().getApiKey() != null) { @@ -131,7 +131,23 @@ public ResponseEntity umlsLogin(Principal principal, @RequestBody String @GetMapping("/umls-credentials/status") public ResponseEntity checkUserLogin(Principal principal) { return vsacService.validateUmlsInformation(principal.getName()) - ? ResponseEntity.ok().body(Boolean.TRUE) - : new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + ? ResponseEntity.ok().body(Boolean.TRUE) + : new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + } + + @PostMapping(path = "/update-code-systems", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity> retrieveAndUpdateCodeSystems(Principal principal) { + final String username = principal.getName(); + Optional umlsUser = vsacService.findByHarpId(username); + + if (umlsUser.isPresent() && !StringUtils.isBlank(umlsUser.get().getApiKey())) { + return ResponseEntity.ok().body(fhirTerminologyService.retrieveAllCodeSystems(umlsUser.get())); + } + else{ + log.error( + "Unable to Retrieve List of code systems, " + + "UMLS Authentication Key Not found for user : [{}}]", + username); + throw new VsacUnauthorizedException("Please login to UMLS before proceeding"); } } } diff --git a/src/main/java/gov/cms/madie/terminology/models/CodeSystem.java b/src/main/java/gov/cms/madie/terminology/models/CodeSystem.java new file mode 100644 index 0000000..971f549 --- /dev/null +++ b/src/main/java/gov/cms/madie/terminology/models/CodeSystem.java @@ -0,0 +1,26 @@ +package gov.cms.madie.terminology.models; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.Instant; + +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@Data +@AllArgsConstructor +@Builder(toBuilder = true) +@NoArgsConstructor +@Document + +public class CodeSystem { + @Id private String id; + private String name; + private String version; + private Identifier identifier; // identifier[0] of identifier List + private Meta meta; + private Instant lastUpdated; +} diff --git a/src/main/java/gov/cms/madie/terminology/models/Identifier.java b/src/main/java/gov/cms/madie/terminology/models/Identifier.java new file mode 100644 index 0000000..aa09ed8 --- /dev/null +++ b/src/main/java/gov/cms/madie/terminology/models/Identifier.java @@ -0,0 +1,5 @@ +package gov.cms.madie.terminology.models; +public class Identifier { + private String system; + private String value; +} diff --git a/src/main/java/gov/cms/madie/terminology/models/Meta.java b/src/main/java/gov/cms/madie/terminology/models/Meta.java new file mode 100644 index 0000000..d97a467 --- /dev/null +++ b/src/main/java/gov/cms/madie/terminology/models/Meta.java @@ -0,0 +1,9 @@ +package gov.cms.madie.terminology.models; +import java.time.Instant; +import java.util.List; +public class Meta { + private String versionId; + // This is instant at time of query, not to be confused with the actual resource.meta.lastUpdated, which is a timestamp of when vsac posted the version + private Instant lastUpdated; + private List profile; +} diff --git a/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java b/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java index ce5a0fa..26425fc 100644 --- a/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java +++ b/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java @@ -2,6 +2,7 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.parser.IParser; +import gov.cms.madie.models.cql.terminology.VsacCode; import gov.cms.madie.models.mapping.CodeSystemEntry; import gov.cms.madie.models.measure.ManifestExpansion; import gov.cms.madie.terminology.dto.QdmValueSet; @@ -108,4 +109,30 @@ private List getValueSetConcepts( log.info("No Expansion codes are found for the valueSet oid : [{}]", valueSet.getId()); return List.of(); } + +// call individual + public List retrieveCodeSystemsPage(UmlsUser umlsUser){ + IParser parser = fhirContext.newJsonParser(); + String responseString = fhirTerminologyServiceWebClient.getCodeSystemsPage(0, 100, umlsUser.getApiKey()); + Bundle CodeSystemsBundle = parser.parseResource(Bundle.class, responseString); + var CodeSystemsOptions = new ArrayList(); + CodeSystemsBundle + .getEntry() + .forEach( + entry -> + CodeSystemsOptions.add( + ManifestExpansion.builder() + .id(entry.getResource().getIdPart()) + .fullUrl(entry.getFullUrl()) + .build())); + return CodeSystemsOptions; + } + + public List retrieveAllCodeSystems(UmlsUser umlsUser) { + // starting at count 100, offset zero, we want to keep requesting information until we can't. + return retrieveCodeSystemsPage(umlsUser); +// String codeSystem = FhirTerminologyServiceWebClient.getCodeSystem(0, 0, umlsUser.getApiKey()); +// return codeSystem; + } + } diff --git a/src/main/java/gov/cms/madie/terminology/util/TerminologyServiceUtil.java b/src/main/java/gov/cms/madie/terminology/util/TerminologyServiceUtil.java index f94b43f..994dc47 100644 --- a/src/main/java/gov/cms/madie/terminology/util/TerminologyServiceUtil.java +++ b/src/main/java/gov/cms/madie/terminology/util/TerminologyServiceUtil.java @@ -63,6 +63,13 @@ public static URI buildRetrieveCodeUri(String baseUrl, String codePath) { .toUri(); } + public static URI buildRetrieveCodeSystemsUri(String baseUrl, Integer offset, Integer count) { + // http://uat-cts.nlm.nih.gov/fhir/res/CodeSystem?_offset=100&_count=100 + return UriComponentsBuilder.fromUriString(baseUrl) + .queryParam("_offset", Integer.toString(offset)) + .queryParam("_count", Integer.toString(count)) + .buildAndExpand().encode().toUri(); + } public static String buildCodePath( String codeSystemName, String codeSystemVersion, String codeId) { // "/CodeSystem/LOINC22/Version/2.67/Code/21112-8/Info"; diff --git a/src/main/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClient.java b/src/main/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClient.java index de97304..4c3a39e 100644 --- a/src/main/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClient.java +++ b/src/main/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClient.java @@ -1,5 +1,6 @@ package gov.cms.madie.terminology.webclient; +import gov.cms.madie.terminology.util.TerminologyServiceUtil; import gov.cms.madie.models.measure.ManifestExpansion; import gov.cms.madie.terminology.dto.ValueSetsSearchCriteria; import gov.cms.madie.terminology.util.TerminologyServiceUtil; @@ -22,11 +23,14 @@ public class FhirTerminologyServiceWebClient { private final WebClient fhirTerminologyWebClient; private final String manifestPath; + private final String codeSystemPath; private final String defaultProfile; public FhirTerminologyServiceWebClient( @Value("${client.fhir-terminology-service.base-url}") String fhirTerminologyServiceBaseUrl, @Value("${client.fhir-terminology-service.manifests-urn}") String manifestUrn, + @Value("${client.fhir-terminology-service.code-system-urn}") String codeSystemUrn) { + @Value("${client.fhir-terminology-service.manifests-urn}") String manifestUrn, @Value("${client.default_profile}") String defaultProfile) { fhirTerminologyWebClient = WebClient.builder() @@ -34,6 +38,7 @@ public FhirTerminologyServiceWebClient( .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .build(); this.manifestPath = manifestUrn; + this.codeSystemPath = codeSystemUrn; this.defaultProfile = defaultProfile; } @@ -54,6 +59,26 @@ public String getManifestBundle(String apiKey) { }) .block(); } + public String getCodeSystemsPage(Integer offset, Integer count, String apiKey) { + // https://uat-cts.nlm.nih.gov/fhir/res/CodeSystem?_offset=0&_count=100 + URI codeUri = TerminologyServiceUtil.buildRetrieveCodeSystemsUri(codeSystemPath, offset, count); + log.debug("Retrieving codeSystems at {}, offset {}, count {}", codeSystemPath, offset, count); + return fhirTerminologyWebClient + .get() + .uri(codeUri) + .headers(headers -> headers.setBasicAuth("apikey", apiKey)) + .exchangeToMono( + clientResponse -> { + if (clientResponse.statusCode().equals(HttpStatus.BAD_REQUEST) + || clientResponse.statusCode().equals(HttpStatus.OK)) { + return clientResponse.bodyToMono(String.class); + } else { + log.debug("Received NON-OK response while retrieving codePath"); + return clientResponse.createException().flatMap(Mono::error); + } + }) + .block(); + } public String getValueSetResource( String apiKey, @@ -82,3 +107,4 @@ public String getValueSetResource( .block(); } } + diff --git a/src/main/java/gov/cms/madie/terminology/webclient/TerminologyServiceWebClient.java b/src/main/java/gov/cms/madie/terminology/webclient/TerminologyServiceWebClient.java index 5a8dd1a..3268251 100644 --- a/src/main/java/gov/cms/madie/terminology/webclient/TerminologyServiceWebClient.java +++ b/src/main/java/gov/cms/madie/terminology/webclient/TerminologyServiceWebClient.java @@ -25,10 +25,10 @@ public class TerminologyServiceWebClient { private final String defaultProfile; public TerminologyServiceWebClient( - WebClient.Builder webClientBuilder, - @Value("${client.vsac_base_url}") String baseUrl, - @Value("${client.valueset_endpoint}") String valueSetEndpoint, - @Value("${client.default_profile}") String defaultProfile) { + WebClient.Builder webClientBuilder, + @Value("${client.vsac_base_url}") String baseUrl, + @Value("${client.valueset_endpoint}") String valueSetEndpoint, + @Value("${client.default_profile}") String defaultProfile) { this.terminologyClient = webClientBuilder.baseUrl(baseUrl).build(); this.baseUrl = baseUrl; @@ -38,58 +38,58 @@ public TerminologyServiceWebClient( } public RetrieveMultipleValueSetsResponse getValueSet( - String oid, - String apiKey, - String profile, - String includeDraft, - String release, - String version) { + String oid, + String apiKey, + String profile, + String includeDraft, + String release, + String version) { URI valuesetURI = getValueSetURI(oid, profile, includeDraft, release, version); log.debug("valuesetURI = " + valuesetURI.getQuery()); Mono responseMono = - terminologyClient - .get() - .uri(valuesetURI) - .headers(headers -> headers.setBasicAuth("apikey", apiKey)) - .retrieve() - .onStatus(HttpStatusCode::is5xxServerError, ClientResponse::createException) - .onStatus(HttpStatusCode::is4xxClientError, ClientResponse::createException) - .bodyToMono(RetrieveMultipleValueSetsResponse.class); + terminologyClient + .get() + .uri(valuesetURI) + .headers(headers -> headers.setBasicAuth("apikey", apiKey)) + .retrieve() + .onStatus(HttpStatusCode::is5xxServerError, ClientResponse::createException) + .onStatus(HttpStatusCode::is4xxClientError, ClientResponse::createException) + .bodyToMono(RetrieveMultipleValueSetsResponse.class); // temp use of block until fixing 401 issue return responseMono.block(); } protected URI getValueSetURI( - String oid, String profile, String includeDraft, String release, String version) { + String oid, String profile, String includeDraft, String release, String version) { profile = StringUtils.isBlank(profile) ? defaultProfile : profile; return TerminologyServiceUtil.buildRetrieveMultipleValueSetsUri( - baseUrl, valueSetEndpoint, oid, profile, includeDraft, release, version); + baseUrl, valueSetEndpoint, oid, profile, includeDraft, release, version); } /** * @param codePath code path build to call VSAC services. - * @param apiKey user's UMLS ApiKey. + * @param apiKey user's UMLS ApiKey. * @return the response from VSAC is the statusCode is either 200 or 400 Status Code: 200 - * indicates a valid code Status Code, 400 indicates either CodeSystem or CodeSystem version - * or Code is not found. + * indicates a valid code Status Code, 400 indicates either CodeSystem or CodeSystem version + * or Code is not found. */ public VsacCode getCode(String codePath, String apiKey) { URI codeUri = TerminologyServiceUtil.buildRetrieveCodeUri(baseUrl, codePath); log.debug("Retrieving vsacCode for codePath {}", codePath); return terminologyClient - .get() - .uri(codeUri) - .headers(headers -> headers.setBasicAuth("apikey", apiKey)) - .exchangeToMono( - clientResponse -> { - if (clientResponse.statusCode().equals(HttpStatus.BAD_REQUEST) - || clientResponse.statusCode().equals(HttpStatus.OK)) { - return clientResponse.bodyToMono(VsacCode.class); - } else { - log.debug("Received NON-OK response while retrieving codePath {}", codePath); - return clientResponse.createException().flatMap(Mono::error); - } - }) - .block(); + .get() + .uri(codeUri) + .headers(headers -> headers.setBasicAuth("apikey", apiKey)) + .exchangeToMono( + clientResponse -> { + if (clientResponse.statusCode().equals(HttpStatus.BAD_REQUEST) + || clientResponse.statusCode().equals(HttpStatus.OK)) { + return clientResponse.bodyToMono(VsacCode.class); + } else { + log.debug("Received NON-OK response while retrieving codePath {}", codePath); + return clientResponse.createException().flatMap(Mono::error); + } + }) + .block(); } -} +} \ No newline at end of file diff --git a/src/test/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClientTest.java b/src/test/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClientTest.java index 9f95029..dc6e301 100644 --- a/src/test/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClientTest.java +++ b/src/test/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClientTest.java @@ -21,6 +21,7 @@ class FhirTerminologyServiceWebClientTest { private static final String MOCK_RESPONSE_STRING = "test-response"; private static final String MOCK_MANIFEST_URN = "/manifestUrn"; + private static final String MOCK_CODE_SYSTEM_URN = "/codeSystemUrn"; private static final String MOCK_API_KEY = "test-api-key"; private static final String DEFAULT_PROFILE = "Most Recent Code System Versions in VSAC"; public static MockWebServer mockBackEnd; diff --git a/src/test/java/gov/cms/madie/terminology/webclient/TerminologyServiceWebClientTest.java b/src/test/java/gov/cms/madie/terminology/webclient/TerminologyServiceWebClientTest.java index fbc8a0f..3ef7fd9 100644 --- a/src/test/java/gov/cms/madie/terminology/webclient/TerminologyServiceWebClientTest.java +++ b/src/test/java/gov/cms/madie/terminology/webclient/TerminologyServiceWebClientTest.java @@ -46,7 +46,6 @@ public class TerminologyServiceWebClientTest { "/valueset?id={oid}&profile={profile}&includeDraft={includeDraft}"; private static final String DEFAULT_PROFILE = "eCQM Update 2022-05-05"; private static final String API_KEY = UUID.randomUUID().toString(); - @BeforeEach void setUp() { when(webClientBuilderMock.baseUrl(anyString())).thenReturn(webClientBuilderMock); From 52270a6c78e184ac83af56d59f11e478e13fa812 Mon Sep 17 00:00:00 2001 From: mcmcphillips Date: Tue, 26 Mar 2024 12:36:59 -0700 Subject: [PATCH 02/14] MAT-6919: add urn --- src/main/resources/application.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 0b63a54..e3f6637 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -25,6 +25,7 @@ client: fhir-terminology-service: base-url: ${FHIR_TERMINOLOGY_BASE_URL:https://uat-cts.nlm.nih.gov/fhir} manifests-urn: /Library + code-system-urn: /res/codeSystem spring: session: From aad60ab04aadb6bdcf518db62bf1c9696a8c3f81 Mon Sep 17 00:00:00 2001 From: mcmcphillips Date: Tue, 26 Mar 2024 14:18:54 -0700 Subject: [PATCH 03/14] MAT-6916 more partial --- .../terminology/config/WebFluxConfiguration.java | 13 +++++++++++++ .../terminology/controller/VsacController.java | 2 +- .../cms/madie/terminology/models/CodeSystem.java | 2 +- .../terminology/service/FhirTerminologyService.java | 5 ++--- .../webclient/FhirTerminologyServiceWebClient.java | 3 ++- 5 files changed, 19 insertions(+), 6 deletions(-) create mode 100644 src/main/java/gov/cms/madie/terminology/config/WebFluxConfiguration.java diff --git a/src/main/java/gov/cms/madie/terminology/config/WebFluxConfiguration.java b/src/main/java/gov/cms/madie/terminology/config/WebFluxConfiguration.java new file mode 100644 index 0000000..d261177 --- /dev/null +++ b/src/main/java/gov/cms/madie/terminology/config/WebFluxConfiguration.java @@ -0,0 +1,13 @@ +package gov.cms.madie.terminology.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.http.codec.ServerCodecConfigurer; +import org.springframework.web.reactive.config.WebFluxConfigurer; + +@Configuration +public class WebFluxConfiguration implements WebFluxConfigurer { + @Override + public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) { + configurer.defaultCodecs().maxInMemorySize(500 * 1024); + } +} \ No newline at end of file diff --git a/src/main/java/gov/cms/madie/terminology/controller/VsacController.java b/src/main/java/gov/cms/madie/terminology/controller/VsacController.java index 3f808e8..64fd92b 100644 --- a/src/main/java/gov/cms/madie/terminology/controller/VsacController.java +++ b/src/main/java/gov/cms/madie/terminology/controller/VsacController.java @@ -135,7 +135,7 @@ public ResponseEntity checkUserLogin(Principal principal) { : new ResponseEntity<>(HttpStatus.UNAUTHORIZED); } - @PostMapping(path = "/update-code-systems", produces = MediaType.APPLICATION_JSON_VALUE) + @GetMapping(path = "/update-code-systems", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity> retrieveAndUpdateCodeSystems(Principal principal) { final String username = principal.getName(); Optional umlsUser = vsacService.findByHarpId(username); diff --git a/src/main/java/gov/cms/madie/terminology/models/CodeSystem.java b/src/main/java/gov/cms/madie/terminology/models/CodeSystem.java index 971f549..91cd396 100644 --- a/src/main/java/gov/cms/madie/terminology/models/CodeSystem.java +++ b/src/main/java/gov/cms/madie/terminology/models/CodeSystem.java @@ -17,7 +17,7 @@ @Document public class CodeSystem { - @Id private String id; + @Id private String id; //version ID private String name; private String version; private Identifier identifier; // identifier[0] of identifier List diff --git a/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java b/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java index 26425fc..6c900df 100644 --- a/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java +++ b/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java @@ -129,10 +129,9 @@ public List retrieveCodeSystemsPage(UmlsUser umlsUser){ } public List retrieveAllCodeSystems(UmlsUser umlsUser) { - // starting at count 100, offset zero, we want to keep requesting information until we can't. + return retrieveCodeSystemsPage(umlsUser); -// String codeSystem = FhirTerminologyServiceWebClient.getCodeSystem(0, 0, umlsUser.getApiKey()); -// return codeSystem; + // save } } diff --git a/src/main/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClient.java b/src/main/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClient.java index 4c3a39e..1c4f818 100644 --- a/src/main/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClient.java +++ b/src/main/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClient.java @@ -36,6 +36,7 @@ public FhirTerminologyServiceWebClient( WebClient.builder() .baseUrl(fhirTerminologyServiceBaseUrl) .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .codecs(clientCodecConfigurer -> clientCodecConfigurer.defaultCodecs().maxInMemorySize(-1)) .build(); this.manifestPath = manifestUrn; this.codeSystemPath = codeSystemUrn; @@ -65,7 +66,7 @@ public String getCodeSystemsPage(Integer offset, Integer count, String apiKey) { log.debug("Retrieving codeSystems at {}, offset {}, count {}", codeSystemPath, offset, count); return fhirTerminologyWebClient .get() - .uri(codeUri) + .uri(codeUri.toString()) .headers(headers -> headers.setBasicAuth("apikey", apiKey)) .exchangeToMono( clientResponse -> { From d38cc2cac790e858b7633ceb41001f9177006f0d Mon Sep 17 00:00:00 2001 From: mcmcphillips Date: Wed, 27 Mar 2024 11:12:10 -0700 Subject: [PATCH 04/14] MAT-6916: Implement basic endpoint for triggering database update --- .../config/WebFluxConfiguration.java | 10 +-- .../controller/VsacController.java | 50 ++++------- .../VsacFhirTerminologyController.java | 19 ++++ .../madie/terminology/models/CodeSystem.java | 12 ++- .../madie/terminology/models/Identifier.java | 5 -- .../cms/madie/terminology/models/Meta.java | 9 -- .../repositories/CodeSystemRepository.java | 10 +++ .../service/FhirTerminologyService.java | 89 ++++++++++++++----- .../util/TerminologyServiceUtil.java | 11 ++- .../FhirTerminologyServiceWebClient.java | 48 +++++----- .../TerminologyServiceWebClient.java | 76 ++++++++-------- .../FhirTerminologyServiceWebClientTest.java | 3 +- .../TerminologyServiceWebClientTest.java | 1 + 13 files changed, 196 insertions(+), 147 deletions(-) delete mode 100644 src/main/java/gov/cms/madie/terminology/models/Identifier.java delete mode 100644 src/main/java/gov/cms/madie/terminology/models/Meta.java create mode 100644 src/main/java/gov/cms/madie/terminology/repositories/CodeSystemRepository.java diff --git a/src/main/java/gov/cms/madie/terminology/config/WebFluxConfiguration.java b/src/main/java/gov/cms/madie/terminology/config/WebFluxConfiguration.java index d261177..58e8853 100644 --- a/src/main/java/gov/cms/madie/terminology/config/WebFluxConfiguration.java +++ b/src/main/java/gov/cms/madie/terminology/config/WebFluxConfiguration.java @@ -6,8 +6,8 @@ @Configuration public class WebFluxConfiguration implements WebFluxConfigurer { - @Override - public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) { - configurer.defaultCodecs().maxInMemorySize(500 * 1024); - } -} \ No newline at end of file + @Override + public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) { + configurer.defaultCodecs().maxInMemorySize(500 * 1024); + } +} diff --git a/src/main/java/gov/cms/madie/terminology/controller/VsacController.java b/src/main/java/gov/cms/madie/terminology/controller/VsacController.java index 64fd92b..a73fc16 100644 --- a/src/main/java/gov/cms/madie/terminology/controller/VsacController.java +++ b/src/main/java/gov/cms/madie/terminology/controller/VsacController.java @@ -38,19 +38,19 @@ public class VsacController { @GetMapping(path = "/valueset", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity getValueSet( - Principal principal, - @RequestParam String oid, - @RequestParam(required = false, name = "profile") String profile, - @RequestParam(required = false, name = "includeDraft") String includeDraft, - @RequestParam(required = false, name = "release") String release, - @RequestParam(required = false, name = "version") String version) { + Principal principal, + @RequestParam String oid, + @RequestParam(required = false, name = "profile") String profile, + @RequestParam(required = false, name = "includeDraft") String includeDraft, + @RequestParam(required = false, name = "release") String release, + @RequestParam(required = false, name = "version") String version) { log.debug("Entering: getValueSet()"); final String username = principal.getName(); Optional umlsUser = vsacService.findByHarpId(username); if (umlsUser.isPresent()) { RetrieveMultipleValueSetsResponse valuesetResponse = - vsacService.getValueSet(oid, umlsUser.get(), profile, includeDraft, release, version); + vsacService.getValueSet(oid, umlsUser.get(), profile, includeDraft, release, version); ValueSet fhirValueSet = vsacService.convertToFHIRValueSet(valuesetResponse); log.debug("valueset id = " + fhirValueSet.getId()); @@ -67,7 +67,7 @@ protected String serializeFhirValueset(ValueSet fhirValueSet) { @PutMapping(path = "/value-sets/searches", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity searchValueSets( - Principal principal, @RequestBody ValueSetsSearchCriteria searchCriteria) { + Principal principal, @RequestBody ValueSetsSearchCriteria searchCriteria) { log.debug("VsacController::getValueSets"); final String username = principal.getName(); @@ -75,11 +75,11 @@ public ResponseEntity searchValueSets( if (umlsUser.isPresent()) { List vsacValueSets = - vsacService.getValueSets(searchCriteria, umlsUser.get()); + vsacService.getValueSets(searchCriteria, umlsUser.get()); List fhirValueSets = vsacService.convertToFHIRValueSets(vsacValueSets); String serializedValueSets = - fhirValueSets.stream().map(this::serializeFhirValueset).collect(Collectors.joining(", ")); + fhirValueSets.stream().map(this::serializeFhirValueset).collect(Collectors.joining(", ")); return ResponseEntity.ok().body("[" + serializedValueSets + "]"); } @@ -88,14 +88,14 @@ public ResponseEntity searchValueSets( @PutMapping("/qdm/value-sets/searches") public ResponseEntity> getQdmValueSets( - Principal principal, @RequestBody ValueSetsSearchCriteria searchCriteria) { + Principal principal, @RequestBody ValueSetsSearchCriteria searchCriteria) { log.debug("VsacController::getQdmValueSets"); final String username = principal.getName(); Optional umlsUser = vsacService.findByHarpId(username); if (umlsUser.isPresent()) { List qdmValueSets = - vsacService.getValueSetsInQdmFormat(searchCriteria, umlsUser.get()); + vsacService.getValueSetsInQdmFormat(searchCriteria, umlsUser.get()); return ResponseEntity.ok().body(qdmValueSets); } @@ -104,9 +104,9 @@ public ResponseEntity> getQdmValueSets( @PutMapping(path = "/validations/codes", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity> validateCodes( - Principal principal, - @RequestBody List cqlCodes, - @RequestParam(required = false, defaultValue = "FHIR") String model) { + Principal principal, + @RequestBody List cqlCodes, + @RequestParam(required = false, defaultValue = "FHIR") String model) { final String username = principal.getName(); Optional umlsUser = vsacService.findByHarpId(username); if (umlsUser.isPresent() && umlsUser.get().getApiKey() != null) { @@ -131,23 +131,7 @@ public ResponseEntity umlsLogin(Principal principal, @RequestBody String @GetMapping("/umls-credentials/status") public ResponseEntity checkUserLogin(Principal principal) { return vsacService.validateUmlsInformation(principal.getName()) - ? ResponseEntity.ok().body(Boolean.TRUE) - : new ResponseEntity<>(HttpStatus.UNAUTHORIZED); - } - - @GetMapping(path = "/update-code-systems", produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity> retrieveAndUpdateCodeSystems(Principal principal) { - final String username = principal.getName(); - Optional umlsUser = vsacService.findByHarpId(username); - - if (umlsUser.isPresent() && !StringUtils.isBlank(umlsUser.get().getApiKey())) { - return ResponseEntity.ok().body(fhirTerminologyService.retrieveAllCodeSystems(umlsUser.get())); - } - else{ - log.error( - "Unable to Retrieve List of code systems, " - + "UMLS Authentication Key Not found for user : [{}}]", - username); - throw new VsacUnauthorizedException("Please login to UMLS before proceeding"); } + ? ResponseEntity.ok().body(Boolean.TRUE) + : new ResponseEntity<>(HttpStatus.UNAUTHORIZED); } } diff --git a/src/main/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyController.java b/src/main/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyController.java index 2462dd2..ebbb103 100644 --- a/src/main/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyController.java +++ b/src/main/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyController.java @@ -4,12 +4,14 @@ import gov.cms.madie.terminology.dto.QdmValueSet; import gov.cms.madie.terminology.dto.ValueSetsSearchCriteria; import gov.cms.madie.terminology.exceptions.VsacUnauthorizedException; +import gov.cms.madie.terminology.models.CodeSystem; import gov.cms.madie.terminology.models.UmlsUser; import gov.cms.madie.terminology.service.FhirTerminologyService; import gov.cms.madie.terminology.service.VsacService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -59,4 +61,21 @@ public ResponseEntity> getValueSetsExpansions( username); throw new VsacUnauthorizedException("Please login to UMLS before proceeding"); } + + @GetMapping(path = "/update-code-systems", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity> retrieveAndUpdateCodeSystems(Principal principal) { + final String username = principal.getName(); + Optional umlsUser = vsacService.findByHarpId(username); + + if (umlsUser.isPresent() && !StringUtils.isBlank(umlsUser.get().getApiKey())) { + return ResponseEntity.ok() + .body(fhirTerminologyService.retrieveAllCodeSystems(umlsUser.get())); + } else { + log.error( + "Unable to Retrieve List of code systems, " + + "UMLS Authentication Key Not found for user : [{}}]", + username); + throw new VsacUnauthorizedException("Please login to UMLS before proceeding"); + } + } } diff --git a/src/main/java/gov/cms/madie/terminology/models/CodeSystem.java b/src/main/java/gov/cms/madie/terminology/models/CodeSystem.java index 91cd396..961ea2a 100644 --- a/src/main/java/gov/cms/madie/terminology/models/CodeSystem.java +++ b/src/main/java/gov/cms/madie/terminology/models/CodeSystem.java @@ -15,12 +15,10 @@ @Builder(toBuilder = true) @NoArgsConstructor @Document - public class CodeSystem { - @Id private String id; //version ID - private String name; - private String version; - private Identifier identifier; // identifier[0] of identifier List - private Meta meta; - private Instant lastUpdated; + @Id private String versionId; // meta().versionId + private String name; + private String version; + private String value; // identifier[0].value oid of identifier List + private Instant lastUpdated; // when we got it } diff --git a/src/main/java/gov/cms/madie/terminology/models/Identifier.java b/src/main/java/gov/cms/madie/terminology/models/Identifier.java deleted file mode 100644 index aa09ed8..0000000 --- a/src/main/java/gov/cms/madie/terminology/models/Identifier.java +++ /dev/null @@ -1,5 +0,0 @@ -package gov.cms.madie.terminology.models; -public class Identifier { - private String system; - private String value; -} diff --git a/src/main/java/gov/cms/madie/terminology/models/Meta.java b/src/main/java/gov/cms/madie/terminology/models/Meta.java deleted file mode 100644 index d97a467..0000000 --- a/src/main/java/gov/cms/madie/terminology/models/Meta.java +++ /dev/null @@ -1,9 +0,0 @@ -package gov.cms.madie.terminology.models; -import java.time.Instant; -import java.util.List; -public class Meta { - private String versionId; - // This is instant at time of query, not to be confused with the actual resource.meta.lastUpdated, which is a timestamp of when vsac posted the version - private Instant lastUpdated; - private List profile; -} diff --git a/src/main/java/gov/cms/madie/terminology/repositories/CodeSystemRepository.java b/src/main/java/gov/cms/madie/terminology/repositories/CodeSystemRepository.java new file mode 100644 index 0000000..e1e2128 --- /dev/null +++ b/src/main/java/gov/cms/madie/terminology/repositories/CodeSystemRepository.java @@ -0,0 +1,10 @@ +package gov.cms.madie.terminology.repositories; + +import gov.cms.madie.terminology.models.CodeSystem; +import org.springframework.data.mongodb.repository.MongoRepository; +import java.util.Optional; + +public interface CodeSystemRepository extends MongoRepository { + Optional findByVersionId(String versionId); + +} diff --git a/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java b/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java index 6c900df..6eb9028 100644 --- a/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java +++ b/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java @@ -2,12 +2,13 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.parser.IParser; -import gov.cms.madie.models.cql.terminology.VsacCode; import gov.cms.madie.models.mapping.CodeSystemEntry; import gov.cms.madie.models.measure.ManifestExpansion; import gov.cms.madie.terminology.dto.QdmValueSet; import gov.cms.madie.terminology.dto.ValueSetsSearchCriteria; +import gov.cms.madie.terminology.models.CodeSystem; import gov.cms.madie.terminology.models.UmlsUser; +import gov.cms.madie.terminology.repositories.CodeSystemRepository; import gov.cms.madie.terminology.util.TerminologyServiceUtil; import gov.cms.madie.terminology.webclient.FhirTerminologyServiceWebClient; import lombok.RequiredArgsConstructor; @@ -16,7 +17,9 @@ import org.hl7.fhir.r4.model.ValueSet; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; +import org.springframework.web.util.UriComponentsBuilder; +import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -28,7 +31,7 @@ public class FhirTerminologyService { private final FhirContext fhirContext; private final FhirTerminologyServiceWebClient fhirTerminologyServiceWebClient; private final MappingService mappingService; - + private final CodeSystemRepository codeSystemRepository; @Cacheable("manifest-list") public List getManifests(UmlsUser umlsUser) { IParser parser = fhirContext.newJsonParser(); @@ -110,28 +113,72 @@ private List getValueSetConcepts( return List.of(); } -// call individual - public List retrieveCodeSystemsPage(UmlsUser umlsUser){ + // one to call only, one to mutate and build + public Bundle retrieveCodeSystemsPage(UmlsUser umlsUser, Integer offset, Integer count) { IParser parser = fhirContext.newJsonParser(); - String responseString = fhirTerminologyServiceWebClient.getCodeSystemsPage(0, 100, umlsUser.getApiKey()); - Bundle CodeSystemsBundle = parser.parseResource(Bundle.class, responseString); - var CodeSystemsOptions = new ArrayList(); - CodeSystemsBundle - .getEntry() - .forEach( - entry -> - CodeSystemsOptions.add( - ManifestExpansion.builder() - .id(entry.getResource().getIdPart()) - .fullUrl(entry.getFullUrl()) - .build())); - return CodeSystemsOptions; + String responseString = + fhirTerminologyServiceWebClient.getCodeSystemsPage(offset, count, umlsUser.getApiKey()); + return parser.parseResource( + Bundle.class, responseString); } - public List retrieveAllCodeSystems(UmlsUser umlsUser) { - - return retrieveCodeSystemsPage(umlsUser); - // save + private void updateOrInsertAllCodeSystems(List codeSystemList){ + for (CodeSystem codeSystem : codeSystemList) { + Optional existingCodeSystemOptional = codeSystemRepository.findByVersionId(codeSystem.getVersionId()); + if (existingCodeSystemOptional.isPresent()) { + // Update existing CodeSystem + CodeSystem existingCodeSystem = existingCodeSystemOptional.get(); + existingCodeSystem.setName(codeSystem.getName()); + existingCodeSystem.setValue(codeSystem.getValue()); + existingCodeSystem.setLastUpdated(Instant.now()); + codeSystemRepository.save(existingCodeSystem); + log.info("CodeSystem updated: {}", existingCodeSystem); + } else { + // Insert new CodeSystem + codeSystemRepository.save(codeSystem); + log.info("New CodeSystem inserted: {}", codeSystem); + } + } } + private void recursiveRetrieveCodeSystems(UmlsUser umlsUser, Integer offset, Integer count, List allCodeSystems) { + log.info("requesting page offset: {} count: {}", offset, count); + Bundle codeSystemBundle = retrieveCodeSystemsPage(umlsUser, offset, count); + List codeSystemsPage = new ArrayList<>(); // build small list + codeSystemBundle.getEntry().forEach(entry -> { + var codeSystem = (org.hl7.fhir.r4.model.CodeSystem) entry.getResource(); + String codeSystemValue = ""; + if (!codeSystem.getIdentifier().isEmpty()) { + codeSystemValue = codeSystem.getIdentifier().get(0).getValue(); + } + codeSystemsPage.add( + CodeSystem.builder() + .versionId(codeSystem.getMeta().getVersionId()) + .version(codeSystem.getVersion()) + .name(codeSystem.getName()) + .value(codeSystemValue) + .lastUpdated(Instant.now()) + .build()); + }); + allCodeSystems.addAll(codeSystemsPage); // update big list + var links = codeSystemBundle.getLink(); + links.forEach((l) -> { + if (l.getRelation().equals("next")){ + // if next, call self and continue until fail. + UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(l.getUrl()); + String newOffset = builder.build().getQueryParams().getFirst("_offset"); + String newCount = builder.build().getQueryParams().getFirst("_count"); + assert newOffset != null; + assert newCount != null; + recursiveRetrieveCodeSystems(umlsUser, Integer.parseInt(newOffset), Integer.parseInt(newCount), allCodeSystems); + } + }); + } + public List retrieveAllCodeSystems(UmlsUser umlsUser) { + List allCodeSystems = new ArrayList<>(); + recursiveRetrieveCodeSystems(umlsUser, 0, 50, allCodeSystems); + // Once we have all codeSystems, update DB using mongo + updateOrInsertAllCodeSystems(allCodeSystems); + return allCodeSystems; + } } diff --git a/src/main/java/gov/cms/madie/terminology/util/TerminologyServiceUtil.java b/src/main/java/gov/cms/madie/terminology/util/TerminologyServiceUtil.java index 994dc47..ac5e612 100644 --- a/src/main/java/gov/cms/madie/terminology/util/TerminologyServiceUtil.java +++ b/src/main/java/gov/cms/madie/terminology/util/TerminologyServiceUtil.java @@ -64,12 +64,15 @@ public static URI buildRetrieveCodeUri(String baseUrl, String codePath) { } public static URI buildRetrieveCodeSystemsUri(String baseUrl, Integer offset, Integer count) { - // http://uat-cts.nlm.nih.gov/fhir/res/CodeSystem?_offset=100&_count=100 + // http://uat-cts.nlm.nih.gov/fhir/res/CodeSystem?_offset=100&_count=100 return UriComponentsBuilder.fromUriString(baseUrl) - .queryParam("_offset", Integer.toString(offset)) - .queryParam("_count", Integer.toString(count)) - .buildAndExpand().encode().toUri(); + .queryParam("_offset", Integer.toString(offset)) + .queryParam("_count", Integer.toString(count)) + .buildAndExpand() + .encode() + .toUri(); } + public static String buildCodePath( String codeSystemName, String codeSystemVersion, String codeId) { // "/CodeSystem/LOINC22/Version/2.67/Code/21112-8/Info"; diff --git a/src/main/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClient.java b/src/main/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClient.java index 1c4f818..2755d27 100644 --- a/src/main/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClient.java +++ b/src/main/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClient.java @@ -29,14 +29,14 @@ public class FhirTerminologyServiceWebClient { public FhirTerminologyServiceWebClient( @Value("${client.fhir-terminology-service.base-url}") String fhirTerminologyServiceBaseUrl, @Value("${client.fhir-terminology-service.manifests-urn}") String manifestUrn, - @Value("${client.fhir-terminology-service.code-system-urn}") String codeSystemUrn) { - @Value("${client.fhir-terminology-service.manifests-urn}") String manifestUrn, + @Value("${client.fhir-terminology-service.code-system-urn}") String codeSystemUrn, @Value("${client.default_profile}") String defaultProfile) { fhirTerminologyWebClient = WebClient.builder() .baseUrl(fhirTerminologyServiceBaseUrl) .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .codecs(clientCodecConfigurer -> clientCodecConfigurer.defaultCodecs().maxInMemorySize(-1)) + .codecs( + clientCodecConfigurer -> clientCodecConfigurer.defaultCodecs().maxInMemorySize(-1)) .build(); this.manifestPath = manifestUrn; this.codeSystemPath = codeSystemUrn; @@ -60,26 +60,27 @@ public String getManifestBundle(String apiKey) { }) .block(); } - public String getCodeSystemsPage(Integer offset, Integer count, String apiKey) { - // https://uat-cts.nlm.nih.gov/fhir/res/CodeSystem?_offset=0&_count=100 - URI codeUri = TerminologyServiceUtil.buildRetrieveCodeSystemsUri(codeSystemPath, offset, count); - log.debug("Retrieving codeSystems at {}, offset {}, count {}", codeSystemPath, offset, count); - return fhirTerminologyWebClient - .get() - .uri(codeUri.toString()) - .headers(headers -> headers.setBasicAuth("apikey", apiKey)) - .exchangeToMono( - clientResponse -> { - if (clientResponse.statusCode().equals(HttpStatus.BAD_REQUEST) - || clientResponse.statusCode().equals(HttpStatus.OK)) { - return clientResponse.bodyToMono(String.class); - } else { - log.debug("Received NON-OK response while retrieving codePath"); - return clientResponse.createException().flatMap(Mono::error); - } - }) - .block(); - } + + public String getCodeSystemsPage(Integer offset, Integer count, String apiKey) { + // https://uat-cts.nlm.nih.gov/fhir/res/CodeSystem?_offset=0&_count=100 + URI codeUri = TerminologyServiceUtil.buildRetrieveCodeSystemsUri(codeSystemPath, offset, count); + log.debug("Retrieving codeSystems at {}, offset {}, count {}", codeSystemPath, offset, count); + return fhirTerminologyWebClient + .get() + .uri(codeUri.toString()) + .headers(headers -> headers.setBasicAuth("apikey", apiKey)) + .exchangeToMono( + clientResponse -> { + if (clientResponse.statusCode().equals(HttpStatus.BAD_REQUEST) + || clientResponse.statusCode().equals(HttpStatus.OK)) { + return clientResponse.bodyToMono(String.class); + } else { + log.debug("Received NON-OK response while retrieving codePath"); + return clientResponse.createException().flatMap(Mono::error); + } + }) + .block(); + } public String getValueSetResource( String apiKey, @@ -108,4 +109,3 @@ public String getValueSetResource( .block(); } } - diff --git a/src/main/java/gov/cms/madie/terminology/webclient/TerminologyServiceWebClient.java b/src/main/java/gov/cms/madie/terminology/webclient/TerminologyServiceWebClient.java index 3268251..5a8dd1a 100644 --- a/src/main/java/gov/cms/madie/terminology/webclient/TerminologyServiceWebClient.java +++ b/src/main/java/gov/cms/madie/terminology/webclient/TerminologyServiceWebClient.java @@ -25,10 +25,10 @@ public class TerminologyServiceWebClient { private final String defaultProfile; public TerminologyServiceWebClient( - WebClient.Builder webClientBuilder, - @Value("${client.vsac_base_url}") String baseUrl, - @Value("${client.valueset_endpoint}") String valueSetEndpoint, - @Value("${client.default_profile}") String defaultProfile) { + WebClient.Builder webClientBuilder, + @Value("${client.vsac_base_url}") String baseUrl, + @Value("${client.valueset_endpoint}") String valueSetEndpoint, + @Value("${client.default_profile}") String defaultProfile) { this.terminologyClient = webClientBuilder.baseUrl(baseUrl).build(); this.baseUrl = baseUrl; @@ -38,58 +38,58 @@ public TerminologyServiceWebClient( } public RetrieveMultipleValueSetsResponse getValueSet( - String oid, - String apiKey, - String profile, - String includeDraft, - String release, - String version) { + String oid, + String apiKey, + String profile, + String includeDraft, + String release, + String version) { URI valuesetURI = getValueSetURI(oid, profile, includeDraft, release, version); log.debug("valuesetURI = " + valuesetURI.getQuery()); Mono responseMono = - terminologyClient - .get() - .uri(valuesetURI) - .headers(headers -> headers.setBasicAuth("apikey", apiKey)) - .retrieve() - .onStatus(HttpStatusCode::is5xxServerError, ClientResponse::createException) - .onStatus(HttpStatusCode::is4xxClientError, ClientResponse::createException) - .bodyToMono(RetrieveMultipleValueSetsResponse.class); + terminologyClient + .get() + .uri(valuesetURI) + .headers(headers -> headers.setBasicAuth("apikey", apiKey)) + .retrieve() + .onStatus(HttpStatusCode::is5xxServerError, ClientResponse::createException) + .onStatus(HttpStatusCode::is4xxClientError, ClientResponse::createException) + .bodyToMono(RetrieveMultipleValueSetsResponse.class); // temp use of block until fixing 401 issue return responseMono.block(); } protected URI getValueSetURI( - String oid, String profile, String includeDraft, String release, String version) { + String oid, String profile, String includeDraft, String release, String version) { profile = StringUtils.isBlank(profile) ? defaultProfile : profile; return TerminologyServiceUtil.buildRetrieveMultipleValueSetsUri( - baseUrl, valueSetEndpoint, oid, profile, includeDraft, release, version); + baseUrl, valueSetEndpoint, oid, profile, includeDraft, release, version); } /** * @param codePath code path build to call VSAC services. - * @param apiKey user's UMLS ApiKey. + * @param apiKey user's UMLS ApiKey. * @return the response from VSAC is the statusCode is either 200 or 400 Status Code: 200 - * indicates a valid code Status Code, 400 indicates either CodeSystem or CodeSystem version - * or Code is not found. + * indicates a valid code Status Code, 400 indicates either CodeSystem or CodeSystem version + * or Code is not found. */ public VsacCode getCode(String codePath, String apiKey) { URI codeUri = TerminologyServiceUtil.buildRetrieveCodeUri(baseUrl, codePath); log.debug("Retrieving vsacCode for codePath {}", codePath); return terminologyClient - .get() - .uri(codeUri) - .headers(headers -> headers.setBasicAuth("apikey", apiKey)) - .exchangeToMono( - clientResponse -> { - if (clientResponse.statusCode().equals(HttpStatus.BAD_REQUEST) - || clientResponse.statusCode().equals(HttpStatus.OK)) { - return clientResponse.bodyToMono(VsacCode.class); - } else { - log.debug("Received NON-OK response while retrieving codePath {}", codePath); - return clientResponse.createException().flatMap(Mono::error); - } - }) - .block(); + .get() + .uri(codeUri) + .headers(headers -> headers.setBasicAuth("apikey", apiKey)) + .exchangeToMono( + clientResponse -> { + if (clientResponse.statusCode().equals(HttpStatus.BAD_REQUEST) + || clientResponse.statusCode().equals(HttpStatus.OK)) { + return clientResponse.bodyToMono(VsacCode.class); + } else { + log.debug("Received NON-OK response while retrieving codePath {}", codePath); + return clientResponse.createException().flatMap(Mono::error); + } + }) + .block(); } -} \ No newline at end of file +} diff --git a/src/test/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClientTest.java b/src/test/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClientTest.java index dc6e301..cede094 100644 --- a/src/test/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClientTest.java +++ b/src/test/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClientTest.java @@ -41,7 +41,8 @@ void initialize() { testValueSetParams = ValueSetsSearchCriteria.ValueSetParams.builder().oid("test-vs-id").build(); String baseUrl = String.format("http://localhost:%s", mockBackEnd.getPort()); fhirTerminologyServiceWebClient = - new FhirTerminologyServiceWebClient(baseUrl, MOCK_MANIFEST_URN, DEFAULT_PROFILE); + new FhirTerminologyServiceWebClient( + baseUrl, MOCK_MANIFEST_URN, MOCK_CODE_SYSTEM_URN, DEFAULT_PROFILE); } @AfterAll diff --git a/src/test/java/gov/cms/madie/terminology/webclient/TerminologyServiceWebClientTest.java b/src/test/java/gov/cms/madie/terminology/webclient/TerminologyServiceWebClientTest.java index 3ef7fd9..fbc8a0f 100644 --- a/src/test/java/gov/cms/madie/terminology/webclient/TerminologyServiceWebClientTest.java +++ b/src/test/java/gov/cms/madie/terminology/webclient/TerminologyServiceWebClientTest.java @@ -46,6 +46,7 @@ public class TerminologyServiceWebClientTest { "/valueset?id={oid}&profile={profile}&includeDraft={includeDraft}"; private static final String DEFAULT_PROFILE = "eCQM Update 2022-05-05"; private static final String API_KEY = UUID.randomUUID().toString(); + @BeforeEach void setUp() { when(webClientBuilderMock.baseUrl(anyString())).thenReturn(webClientBuilderMock); From 55936e441bfaa70c6a5b85c9c3b6c6d2ba3dfcfc Mon Sep 17 00:00:00 2001 From: mcmcphillips Date: Wed, 27 Mar 2024 11:15:22 -0700 Subject: [PATCH 05/14] MAT-6916: fix casing --- src/main/resources/application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index e3f6637..29eb5b0 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -25,7 +25,7 @@ client: fhir-terminology-service: base-url: ${FHIR_TERMINOLOGY_BASE_URL:https://uat-cts.nlm.nih.gov/fhir} manifests-urn: /Library - code-system-urn: /res/codeSystem + code-system-urn: /res/CodeSystem spring: session: From 1053ae3d2908b28be26fd562ca9c2717c1421277 Mon Sep 17 00:00:00 2001 From: mcmcphillips Date: Thu, 28 Mar 2024 06:49:40 -0700 Subject: [PATCH 06/14] MAT-6916: Update to title+version. Duplicates. --- .../cms/madie/terminology/models/CodeSystem.java | 7 ++++--- .../repositories/CodeSystemRepository.java | 2 +- .../terminology/service/FhirTerminologyService.java | 13 +++++++++---- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/main/java/gov/cms/madie/terminology/models/CodeSystem.java b/src/main/java/gov/cms/madie/terminology/models/CodeSystem.java index 961ea2a..d3b6adb 100644 --- a/src/main/java/gov/cms/madie/terminology/models/CodeSystem.java +++ b/src/main/java/gov/cms/madie/terminology/models/CodeSystem.java @@ -16,9 +16,10 @@ @NoArgsConstructor @Document public class CodeSystem { - @Id private String versionId; // meta().versionId - private String name; + @Id String id; // title + version (both required fields) + private String title; private String version; + private String versionId; private String value; // identifier[0].value oid of identifier List - private Instant lastUpdated; // when we got it + private Instant lastUpdated; // when queried } diff --git a/src/main/java/gov/cms/madie/terminology/repositories/CodeSystemRepository.java b/src/main/java/gov/cms/madie/terminology/repositories/CodeSystemRepository.java index e1e2128..011b83a 100644 --- a/src/main/java/gov/cms/madie/terminology/repositories/CodeSystemRepository.java +++ b/src/main/java/gov/cms/madie/terminology/repositories/CodeSystemRepository.java @@ -5,6 +5,6 @@ import java.util.Optional; public interface CodeSystemRepository extends MongoRepository { - Optional findByVersionId(String versionId); + Optional findById(String id); } diff --git a/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java b/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java index 6eb9028..7c8736e 100644 --- a/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java +++ b/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java @@ -124,13 +124,17 @@ public Bundle retrieveCodeSystemsPage(UmlsUser umlsUser, Integer offset, Integer private void updateOrInsertAllCodeSystems(List codeSystemList){ for (CodeSystem codeSystem : codeSystemList) { - Optional existingCodeSystemOptional = codeSystemRepository.findByVersionId(codeSystem.getVersionId()); + var id = codeSystem.getTitle() + codeSystem.getVersion(); + Optional existingCodeSystemOptional = codeSystemRepository.findById(id); if (existingCodeSystemOptional.isPresent()) { // Update existing CodeSystem CodeSystem existingCodeSystem = existingCodeSystemOptional.get(); - existingCodeSystem.setName(codeSystem.getName()); + + existingCodeSystem.setTitle(codeSystem.getTitle()); + existingCodeSystem.setVersion(codeSystem.getVersion()); existingCodeSystem.setValue(codeSystem.getValue()); existingCodeSystem.setLastUpdated(Instant.now()); + codeSystemRepository.save(existingCodeSystem); log.info("CodeSystem updated: {}", existingCodeSystem); } else { @@ -152,9 +156,10 @@ private void recursiveRetrieveCodeSystems(UmlsUser umlsUser, Integer offset, Int } codeSystemsPage.add( CodeSystem.builder() - .versionId(codeSystem.getMeta().getVersionId()) + .id(codeSystem.getTitle() + codeSystem.getVersion()) + .title(codeSystem.getTitle()) .version(codeSystem.getVersion()) - .name(codeSystem.getName()) + .versionId(codeSystem.getMeta().getVersionId()) .value(codeSystemValue) .lastUpdated(Instant.now()) .build()); From c4c4f517f10b4a206b12a910bef711174bee6ae7 Mon Sep 17 00:00:00 2001 From: mcmcphillips Date: Thu, 28 Mar 2024 07:22:12 -0700 Subject: [PATCH 07/14] MAT-6916: fix pmd violation --- .../terminology/webclient/FhirTerminologyServiceWebClient.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClient.java b/src/main/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClient.java index 2755d27..bb6f3b8 100644 --- a/src/main/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClient.java +++ b/src/main/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClient.java @@ -3,7 +3,6 @@ import gov.cms.madie.terminology.util.TerminologyServiceUtil; import gov.cms.madie.models.measure.ManifestExpansion; import gov.cms.madie.terminology.dto.ValueSetsSearchCriteria; -import gov.cms.madie.terminology.util.TerminologyServiceUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; From 25f70dda0f77e088a27594c6f57dbe37c7c125e5 Mon Sep 17 00:00:00 2001 From: mcmcphillips Date: Thu, 28 Mar 2024 12:09:19 -0700 Subject: [PATCH 08/14] MAT-6916: update tests, add reference for name --- .../madie/terminology/models/CodeSystem.java | 3 + .../service/FhirTerminologyService.java | 7 ++- .../service/FhirTerminologyServiceTest.java | 59 +++++++++++++++++-- .../FhirTerminologyServiceWebClientTest.java | 31 ++++++++++ 4 files changed, 93 insertions(+), 7 deletions(-) diff --git a/src/main/java/gov/cms/madie/terminology/models/CodeSystem.java b/src/main/java/gov/cms/madie/terminology/models/CodeSystem.java index d3b6adb..2fd31ab 100644 --- a/src/main/java/gov/cms/madie/terminology/models/CodeSystem.java +++ b/src/main/java/gov/cms/madie/terminology/models/CodeSystem.java @@ -6,6 +6,7 @@ import lombok.NoArgsConstructor; import java.time.Instant; +import java.util.Date; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @@ -18,8 +19,10 @@ public class CodeSystem { @Id String id; // title + version (both required fields) private String title; + private String name; private String version; private String versionId; private String value; // identifier[0].value oid of identifier List private Instant lastUpdated; // when queried + private Date lastUpdatedUpstream; // when was resource last updated on vsac end } diff --git a/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java b/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java index 7c8736e..ab7baad 100644 --- a/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java +++ b/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java @@ -114,7 +114,7 @@ private List getValueSetConcepts( } // one to call only, one to mutate and build - public Bundle retrieveCodeSystemsPage(UmlsUser umlsUser, Integer offset, Integer count) { + private Bundle retrieveCodeSystemsPage(UmlsUser umlsUser, Integer offset, Integer count) { IParser parser = fhirContext.newJsonParser(); String responseString = fhirTerminologyServiceWebClient.getCodeSystemsPage(offset, count, umlsUser.getApiKey()); @@ -131,10 +131,11 @@ private void updateOrInsertAllCodeSystems(List codeSystemList){ CodeSystem existingCodeSystem = existingCodeSystemOptional.get(); existingCodeSystem.setTitle(codeSystem.getTitle()); + existingCodeSystem.setName(codeSystem.getName()); existingCodeSystem.setVersion(codeSystem.getVersion()); existingCodeSystem.setValue(codeSystem.getValue()); existingCodeSystem.setLastUpdated(Instant.now()); - + existingCodeSystem.setLastUpdatedUpstream(codeSystem.getLastUpdatedUpstream()); codeSystemRepository.save(existingCodeSystem); log.info("CodeSystem updated: {}", existingCodeSystem); } else { @@ -158,10 +159,12 @@ private void recursiveRetrieveCodeSystems(UmlsUser umlsUser, Integer offset, Int CodeSystem.builder() .id(codeSystem.getTitle() + codeSystem.getVersion()) .title(codeSystem.getTitle()) + .name(codeSystem.getName()) .version(codeSystem.getVersion()) .versionId(codeSystem.getMeta().getVersionId()) .value(codeSystemValue) .lastUpdated(Instant.now()) + .lastUpdatedUpstream(codeSystem.getMeta().getLastUpdated()) .build()); }); allCodeSystems.addAll(codeSystemsPage); // update big list diff --git a/src/test/java/gov/cms/madie/terminology/service/FhirTerminologyServiceTest.java b/src/test/java/gov/cms/madie/terminology/service/FhirTerminologyServiceTest.java index e87bd85..bdb330c 100644 --- a/src/test/java/gov/cms/madie/terminology/service/FhirTerminologyServiceTest.java +++ b/src/test/java/gov/cms/madie/terminology/service/FhirTerminologyServiceTest.java @@ -1,6 +1,7 @@ package gov.cms.madie.terminology.service; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.parser.IParser; import com.okta.commons.lang.Collections; import gov.cms.madie.models.mapping.CodeSystemEntry; import gov.cms.madie.models.measure.ManifestExpansion; @@ -8,25 +9,32 @@ import gov.cms.madie.terminology.dto.ValueSetsSearchCriteria; import gov.cms.madie.terminology.helpers.TestHelpers; import gov.cms.madie.terminology.models.UmlsUser; +import gov.cms.madie.terminology.repositories.CodeSystemRepository; import gov.cms.madie.terminology.webclient.FhirTerminologyServiceWebClient; import org.apache.commons.io.FileUtils; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.Identifier; +import org.hl7.fhir.r4.model.Meta; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.Mockito; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.times; + import org.mockito.junit.jupiter.MockitoExtension; import java.io.File; import java.io.IOException; import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; +import java.util.*; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @@ -36,6 +44,7 @@ class FhirTerminologyServiceTest { @Mock FhirContext fhirContext; @Mock MappingService mappingService; @InjectMocks FhirTerminologyService fhirTerminologyService; + @Mock CodeSystemRepository codeSystemRepository; List codeSystemEntries; private UmlsUser umlsUser; private static final String TEST_HARP_ID = "te$tHarpId"; @@ -213,4 +222,44 @@ void getsValueSetsExpansionsForQdm_withNoCodes_When_ManifestExpansionIsProvided( assertEquals("AnkylosingSpondylitis", result.get(0).getDisplayName()); assertEquals(0, result.get(0).getConcepts().size()); } + + @Test + void testRetrieveAllCodeSystems() { + umlsUser = UmlsUser.builder().apiKey(TEST_API_KEY).harpId(TEST_HARP_ID).build(); + when(fhirContext.newJsonParser()).thenReturn(FhirContext.forR4().newJsonParser()); + + Bundle bundle = new Bundle(); + var c1 = new CodeSystem(); + var identifierList = new ArrayList(); + var i1 = new Identifier().setValue("codeUrl"); + identifierList.add(i1); + var m1 = new Meta().setLastUpdated(new Date()).setVersionId("vid"); + c1.setId("titleversion"); + c1.setTitle("title"); + c1.setVersion("version"); + c1.setMeta(m1); + c1.setIdentifier(identifierList); + var c2 = new CodeSystem(); + var identifierList2 = new ArrayList(); + var i2 = new Identifier().setValue("codeUrl"); + identifierList2.add(i2); + var m2 = new Meta().setLastUpdated(new Date()).setVersionId("vid"); + c2.setId("titleversion"); + c2.setTitle("title"); + c2.setVersion("version"); + c2.setMeta(m2); + c2.setIdentifier(identifierList2); + bundle.addEntry().setResource(c1); + bundle.addEntry().setResource(c2); + String mockCodeSystemsResource = "{\"resourceType\":\"Bundle\",\"id\":\"codesystem-search\",\"meta\":{\"lastUpdated\":\"2024-03-28T15:04:59.375-04:00\"},\"type\":\"searchset\",\"total\":831,\"link\":[{\"relation\":\"self\",\"url\":\"http://uat-cts.nlm.nih.gov/fhir/res/CodeSystem?_offset=500&_count=2\"},{\"relation\":\"first\",\"url\":\"http://uat-cts.nlm.nih.gov/fhir/res/CodeSystem?_offset=0&_count=2\"},{\"relation\":\"previous\",\"url\":\"http://uat-cts.nlm.nih.gov/fhir/res/CodeSystem?_offset=498&_count=2\"},{\"relation\":\"last\",\"url\":\"http://uat-cts.nlm.nih.gov/fhir/res/CodeSystem?_offset=829&_count=2\"}],\"entry\":[{\"fullUrl\":\"http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation\",\"resource\":{\"resourceType\":\"CodeSystem\",\"id\":\"ObservationInterpretation\",\"meta\":{\"versionId\":\"1710382394\",\"lastUpdated\":\"2019-04-25T00:00:00.000-04:00\",\"profile\":[\"http://hl7.org/fhir/StructureDefinition/shareablecodesystem\"]},\"url\":\"http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation\",\"identifier\":[{\"system\":\"urn:ietf:rfc:3986\",\"value\":\"urn:oid:2.16.840.1.113883.5.83\"}],\"version\":\"2019-03-01\",\"name\":\"ObservationInterpretation\",\"title\":\"ObservationInterpretation\",\"status\":\"active\",\"experimental\":false,\"date\":\"2019-04-15T00:00:00-04:00\",\"_publisher\":{\"extension\":[{\"url\":\"http://hl7.org/fhir/StructureDefinition/data-absent-reason\",\"valueCode\":\"unknown\"}]},\"content\":\"complete\",\"count\":57,\"concept\":[{\"code\":\"<\",\"display\":\"Offscalelow\"},{\"code\":\">\",\"display\":\"Offscalehigh\"},{\"code\":\"A\",\"display\":\"Abnormal\"},{\"code\":\"AA\",\"display\":\"Criticalabnormal\"},{\"code\":\"AC\",\"display\":\"Anti-complementarysubstancespresent\"},{\"code\":\"B\",\"display\":\"Better\"},{\"code\":\"CAR\",\"display\":\"Carrier\"},{\"code\":\"Carrier\",\"display\":\"Carrier\"},{\"code\":\"D\",\"display\":\"Significantchangedown\"},{\"code\":\"DET\",\"display\":\"Detected\"},{\"code\":\"E\",\"display\":\"Equivocal\"},{\"code\":\"EX\",\"display\":\"outsidethreshold\"},{\"code\":\"EXP\",\"display\":\"Expected\"},{\"code\":\"H\",\"display\":\"High\"},{\"code\":\"H>\",\"display\":\"Significantlyhigh\"},{\"code\":\"HH\",\"display\":\"Criticalhigh\"},{\"code\":\"HM\",\"display\":\"HoldforMedicalReview\"},{\"code\":\"HU\",\"display\":\"Significantlyhigh\"},{\"code\":\"HX\",\"display\":\"abovehighthreshold\"},{\"code\":\"I\",\"display\":\"Intermediate\"},{\"code\":\"IE\",\"display\":\"Insufficientevidence\"},{\"code\":\"IND\",\"display\":\"Indeterminate\"},{\"code\":\"L\",\"display\":\"Low\"},{\"code\":\"L<\",\"display\":\"Significantlylow\"},{\"code\":\"LL\",\"display\":\"Criticallow\"},{\"code\":\"LU\",\"display\":\"Significantlylow\"},{\"code\":\"LX\",\"display\":\"belowlowthreshold\"},{\"code\":\"MS\",\"display\":\"moderatelysusceptible\"},{\"code\":\"N\",\"display\":\"Normal\"},{\"code\":\"NCL\",\"display\":\"NoCLSIdefinedbreakpoint\"},{\"code\":\"ND\",\"display\":\"Notdetected\"},{\"code\":\"NEG\",\"display\":\"Negative\"},{\"code\":\"NR\",\"display\":\"Non-reactive\"},{\"code\":\"NS\",\"display\":\"Non-susceptible\"},{\"code\":\"OBX\",\"display\":\"InterpretationqualifiersinseparateOBXsegments\"},{\"code\":\"ObservationInterpretationDetection\",\"display\":\"ObservationInterpretationDetection\"},{\"code\":\"ObservationInterpretationExpectation\",\"display\":\"ObservationInterpretationExpectation\"},{\"code\":\"POS\",\"display\":\"Positive\"},{\"code\":\"QCF\",\"display\":\"Qualitycontrolfailure\"},{\"code\":\"R\",\"display\":\"Resistant\"},{\"code\":\"RR\",\"display\":\"Reactive\"},{\"code\":\"ReactivityObservationInterpretation\",\"display\":\"ReactivityObservationInterpretation\"},{\"code\":\"S\",\"display\":\"Susceptible\"},{\"code\":\"SDD\",\"display\":\"Susceptible-dosedependent\"},{\"code\":\"SYN-R\",\"display\":\"Synergy-resistant\"},{\"code\":\"SYN-S\",\"display\":\"Synergy-susceptible\"},{\"code\":\"TOX\",\"display\":\"Cytotoxicsubstancepresent\"},{\"code\":\"U\",\"display\":\"Significantchangeup\"},{\"code\":\"UNE\",\"display\":\"Unexpected\"},{\"code\":\"VS\",\"display\":\"verysusceptible\"},{\"code\":\"W\",\"display\":\"Worse\"},{\"code\":\"WR\",\"display\":\"Weaklyreactive\"},{\"code\":\"_GeneticObservationInterpretation\",\"display\":\"GeneticObservationInterpretation\"},{\"code\":\"_ObservationInterpretationChange\",\"display\":\"ObservationInterpretationChange\"},{\"code\":\"_ObservationInterpretationExceptions\",\"display\":\"ObservationInterpretationExceptions\"},{\"code\":\"_ObservationInterpretationNormality\",\"display\":\"ObservationInterpretationNormality\"},{\"code\":\"_ObservationInterpretationSusceptibility\",\"display\":\"ObservationInterpretationSusceptibility\"}]}},{\"fullUrl\":\"http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation\",\"resource\":{\"resourceType\":\"CodeSystem\",\"id\":\"ObservationInterpretation\",\"meta\":{\"versionId\":\"1305437570\",\"lastUpdated\":\"2020-01-16T00:00:00.000-05:00\",\"profile\":[\"http://hl7.org/fhir/StructureDefinition/shareablecodesystem\"]},\"url\":\"http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation\",\"identifier\":[{\"system\":\"urn:ietf:rfc:3986\",\"value\":\"urn:oid:2.16.840.1.113883.5.83\"}],\"version\":\"2019-12-01\",\"name\":\"ObservationInterpretation\",\"title\":\"ObservationInterpretation\",\"status\":\"active\",\"experimental\":false,\"date\":\"2019-12-27T00:00:00-05:00\",\"_publisher\":{\"extension\":[{\"url\":\"http://hl7.org/fhir/StructureDefinition/data-absent-reason\",\"valueCode\":\"unknown\"}]},\"content\":\"complete\",\"count\":57,\"concept\":[{\"code\":\"<\",\"display\":\"Offscalelow\"},{\"code\":\">\",\"display\":\"Offscalehigh\"},{\"code\":\"A\",\"display\":\"Abnormal\"},{\"code\":\"AA\",\"display\":\"Criticalabnormal\"},{\"code\":\"AC\",\"display\":\"Anti-complementarysubstancespresent\"},{\"code\":\"B\",\"display\":\"Better\"},{\"code\":\"CAR\",\"display\":\"Carrier\"},{\"code\":\"Carrier\",\"display\":\"Carrier\"},{\"code\":\"D\",\"display\":\"Significantchangedown\"},{\"code\":\"DET\",\"display\":\"Detected\"},{\"code\":\"E\",\"display\":\"Equivocal\"},{\"code\":\"EX\",\"display\":\"outsidethreshold\"},{\"code\":\"EXP\",\"display\":\"Expected\"},{\"code\":\"H\",\"display\":\"High\"},{\"code\":\"H>\",\"display\":\"Significantlyhigh\"},{\"code\":\"HH\",\"display\":\"Criticalhigh\"},{\"code\":\"HM\",\"display\":\"HoldforMedicalReview\"},{\"code\":\"HU\",\"display\":\"Significantlyhigh\"},{\"code\":\"HX\",\"display\":\"abovehighthreshold\"},{\"code\":\"I\",\"display\":\"Intermediate\"},{\"code\":\"IE\",\"display\":\"Insufficientevidence\"},{\"code\":\"IND\",\"display\":\"Indeterminate\"},{\"code\":\"L\",\"display\":\"Low\"},{\"code\":\"L<\",\"display\":\"Significantlylow\"},{\"code\":\"LL\",\"display\":\"Criticallow\"},{\"code\":\"LU\",\"display\":\"Significantlylow\"},{\"code\":\"LX\",\"display\":\"belowlowthreshold\"},{\"code\":\"MS\",\"display\":\"moderatelysusceptible\"},{\"code\":\"N\",\"display\":\"Normal\"},{\"code\":\"NCL\",\"display\":\"NoCLSIdefinedbreakpoint\"},{\"code\":\"ND\",\"display\":\"Notdetected\"},{\"code\":\"NEG\",\"display\":\"Negative\"},{\"code\":\"NR\",\"display\":\"Non-reactive\"},{\"code\":\"NS\",\"display\":\"Non-susceptible\"},{\"code\":\"OBX\",\"display\":\"InterpretationqualifiersinseparateOBXsegments\"},{\"code\":\"ObservationInterpretationDetection\",\"display\":\"ObservationInterpretationDetection\"},{\"code\":\"ObservationInterpretationExpectation\",\"display\":\"ObservationInterpretationExpectation\"},{\"code\":\"POS\",\"display\":\"Positive\"},{\"code\":\"QCF\",\"display\":\"Qualitycontrolfailure\"},{\"code\":\"R\",\"display\":\"Resistant\"},{\"code\":\"RR\",\"display\":\"Reactive\"},{\"code\":\"ReactivityObservationInterpretation\",\"display\":\"ReactivityObservationInterpretation\"},{\"code\":\"S\",\"display\":\"Susceptible\"},{\"code\":\"SDD\",\"display\":\"Susceptible-dosedependent\"},{\"code\":\"SYN-R\",\"display\":\"Synergy-resistant\"},{\"code\":\"SYN-S\",\"display\":\"Synergy-susceptible\"},{\"code\":\"TOX\",\"display\":\"Cytotoxicsubstancepresent\"},{\"code\":\"U\",\"display\":\"Significantchangeup\"},{\"code\":\"UNE\",\"display\":\"Unexpected\"},{\"code\":\"VS\",\"display\":\"verysusceptible\"},{\"code\":\"W\",\"display\":\"Worse\"},{\"code\":\"WR\",\"display\":\"Weaklyreactive\"},{\"code\":\"_GeneticObservationInterpretation\",\"display\":\"GeneticObservationInterpretation\"},{\"code\":\"_ObservationInterpretationChange\",\"display\":\"ObservationInterpretationChange\"},{\"code\":\"_ObservationInterpretationExceptions\",\"display\":\"ObservationInterpretationExceptions\"},{\"code\":\"_ObservationInterpretationNormality\",\"display\":\"ObservationInterpretationNormality\"},{\"code\":\"_ObservationInterpretationSusceptibility\",\"display\":\"ObservationInterpretationSusceptibility\"}]}}]}"; + when(fhirTerminologyServiceWebClient.getCodeSystemsPage(anyInt(), anyInt(), anyString())) + .thenReturn(mockCodeSystemsResource); + when(codeSystemRepository.findById(anyString())).thenReturn(Optional.empty()); + + List result = fhirTerminologyService.retrieveAllCodeSystems(umlsUser); + + assertEquals(2, result.size()); + verify(codeSystemRepository, times(2)).save(any(gov.cms.madie.terminology.models.CodeSystem.class)); + } + } diff --git a/src/test/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClientTest.java b/src/test/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClientTest.java index cede094..ef257a1 100644 --- a/src/test/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClientTest.java +++ b/src/test/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClientTest.java @@ -149,4 +149,35 @@ void getValueSetResource_ReturnsException() throws InterruptedException { RecordedRequest recordedRequest = mockBackEnd.takeRequest(); assertEquals("/ValueSet/test-vs-id/$expand", recordedRequest.getPath()); } + + @Test + void getCodeSystemsPageSuccessfully_when_ValueSetVersionIsProvided() + throws InterruptedException { + mockBackEnd.enqueue( + new MockResponse() + .setResponseCode(200) + .setBody(MOCK_RESPONSE_STRING) + .addHeader("Content-Type", "application/fhir+json")); + testValueSetParams.setVersion("test-value-set-version-2024"); + String actualResponse = + fhirTerminologyServiceWebClient.getCodeSystemsPage( + 0, 50, MOCK_API_KEY); + assertNotNull(actualResponse); + assertEquals(MOCK_RESPONSE_STRING, actualResponse); + RecordedRequest recordedRequest = mockBackEnd.takeRequest(); + assertEquals( + "/codeSystemUrn?_offset=0&_count=50", + recordedRequest.getPath()); + } + @Test + void getCodeSystemsPage_ReturnsException() throws InterruptedException { + mockBackEnd.enqueue(new MockResponse().setResponseCode(HttpStatus.UNAUTHORIZED.value())); + assertThrows( + WebClientResponseException.class, + () -> + fhirTerminologyServiceWebClient.getCodeSystemsPage( + 0, 50, MOCK_API_KEY)); + RecordedRequest recordedRequest = mockBackEnd.takeRequest(); + assertEquals("/codeSystemUrn?_offset=0&_count=50", recordedRequest.getPath()); + } } From 9f8420806a53b209e3a66033e0870c3c5cf77b1e Mon Sep 17 00:00:00 2001 From: mcmcphillips Date: Thu, 28 Mar 2024 12:29:08 -0700 Subject: [PATCH 09/14] MAT-6916: add test --- .../VsacFhirTerminologyControllerTest.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/test/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyControllerTest.java b/src/test/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyControllerTest.java index 3b4b365..7442f55 100644 --- a/src/test/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyControllerTest.java +++ b/src/test/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyControllerTest.java @@ -4,6 +4,7 @@ import gov.cms.madie.terminology.dto.QdmValueSet; import gov.cms.madie.terminology.dto.ValueSetsSearchCriteria; import gov.cms.madie.terminology.exceptions.VsacUnauthorizedException; +import gov.cms.madie.terminology.models.CodeSystem; import gov.cms.madie.terminology.models.UmlsUser; import gov.cms.madie.terminology.service.FhirTerminologyService; import gov.cms.madie.terminology.service.VsacService; @@ -17,6 +18,7 @@ import org.springframework.http.ResponseEntity; import java.security.Principal; +import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -104,6 +106,31 @@ void testGetValueSetsExpansionsSuccessfully() { assertEquals(response.getBody(), mockQdmValueSets); } + @Test + void retrieveAndUpdateCodeSystemsSuccessfully() { + List mockCodeSystemsPage = new ArrayList<>(); + mockCodeSystemsPage.add( + CodeSystem.builder() + .id("titleversion") + .title("title") + .name("name") + .version("version") + .versionId("vid") + .value("urlval") + .lastUpdated(Instant.now()) + .build()); + Principal principal = mock(Principal.class); + when(principal.getName()).thenReturn(TEST_USER); + when(vsacService.findByHarpId(anyString())).thenReturn(Optional.ofNullable(umlsUser)); + + when(fhirTerminologyService.retrieveAllCodeSystems(any())).thenReturn(mockCodeSystemsPage); + + ResponseEntity> response = + vsacFhirTerminologyController.retrieveAndUpdateCodeSystems(principal); + assertEquals(response.getStatusCode(), HttpStatus.OK); + assertEquals(response.getBody(), mockCodeSystemsPage); + } + @Test void testUnAuthorizedUmlsUserWhileFetchingValueSetsExpansions() { Principal principal = mock(Principal.class); @@ -114,4 +141,14 @@ void testUnAuthorizedUmlsUserWhileFetchingValueSetsExpansions() { VsacUnauthorizedException.class, () -> vsacFhirTerminologyController.getManifests(principal)); } + @Test + void testUnAuthorizedUmlsUserWhileretrievingAndUpdatingCodeSystems() { + Principal principal = mock(Principal.class); + when(principal.getName()).thenReturn(TEST_USER); + when(vsacService.findByHarpId(anyString())) + .thenReturn(Optional.ofNullable(UmlsUser.builder().build())); + assertThrows( + VsacUnauthorizedException.class, + () -> vsacFhirTerminologyController.retrieveAndUpdateCodeSystems(principal)); + } } From f724012ef2f23a6b3d95010f0c001a50a10d7391 Mon Sep 17 00:00:00 2001 From: mcmcphillips Date: Fri, 29 Mar 2024 08:47:25 -0700 Subject: [PATCH 10/14] MAT-6916: Add administrative constraints on endpoint. Add tests --- .../VsacFhirTerminologyController.java | 10 ++++- .../VsacFhirTerminologyControllerMvcTest.java | 37 +++++++++++++++++++ .../VsacFhirTerminologyControllerTest.java | 22 ++++++----- .../service/FhirTerminologyServiceTest.java | 2 - 4 files changed, 58 insertions(+), 13 deletions(-) diff --git a/src/main/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyController.java b/src/main/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyController.java index ebbb103..fc69be8 100644 --- a/src/main/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyController.java +++ b/src/main/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyController.java @@ -8,12 +8,15 @@ import gov.cms.madie.terminology.models.UmlsUser; import gov.cms.madie.terminology.service.FhirTerminologyService; import gov.cms.madie.terminology.service.VsacService; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import org.springframework.security.access.prepost.PreAuthorize; import java.security.Principal; import java.util.List; @@ -63,7 +66,12 @@ public ResponseEntity> getValueSetsExpansions( } @GetMapping(path = "/update-code-systems", produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity> retrieveAndUpdateCodeSystems(Principal principal) { + @PreAuthorize("#request.getHeader('api-key') == #apiKey") + public ResponseEntity> retrieveAndUpdateCodeSystems( + Principal principal, + HttpServletRequest request, + @Value("${admin-api-key}") String apiKey, + @RequestHeader("Authorization") String accessToken) { final String username = principal.getName(); Optional umlsUser = vsacService.findByHarpId(username); diff --git a/src/test/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyControllerMvcTest.java b/src/test/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyControllerMvcTest.java index 5542a51..c6c8215 100644 --- a/src/test/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyControllerMvcTest.java +++ b/src/test/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyControllerMvcTest.java @@ -1,8 +1,10 @@ package gov.cms.madie.terminology.controller; import gov.cms.madie.models.measure.ManifestExpansion; +import gov.cms.madie.models.measure.Measure; import gov.cms.madie.terminology.dto.QdmValueSet; import gov.cms.madie.terminology.dto.ValueSetsSearchCriteria; +import gov.cms.madie.terminology.models.CodeSystem; import gov.cms.madie.terminology.models.UmlsUser; import gov.cms.madie.terminology.service.FhirTerminologyService; import gov.cms.madie.terminology.service.VsacService; @@ -23,12 +25,14 @@ import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.*; import static org.mockito.Mockito.when; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @WebMvcTest(VsacFhirTerminologyController.class) @@ -48,6 +52,8 @@ class VsacFhirTerminologyControllerMvcTest { private final List mockQdmValueSets = new ArrayList<>(); private static final String TEST_USER = "test.user"; private static final String TEST_API_KEY = "te$tKey"; + private static final String ADMIN_TEST_API_KEY_HEADER = "api-key"; + private static final String ADMIN_TEST_API_KEY_HEADER_VALUE = "0a51991c"; @BeforeEach public void setup() { @@ -190,4 +196,35 @@ void testUnAuthorizedUmlsUserWhileGetValueSetsExpansionsMvc() throws Exception { .andReturn(); assertThat(result.getResponse().getStatus(), is(equalTo(401))); } + @Test + public void testRetrieveAndUpdateCodeSystemsSuccessfully() throws Exception { + Principal principal = mock(Principal.class); + when(principal.getName()).thenReturn(TEST_USER); + when(vsacService.findByHarpId(anyString())).thenReturn(Optional.empty()); + MvcResult result = + mockMvc + .perform( + MockMvcRequestBuilders.get("/terminology/update-code-systems") + .with(csrf()) + .with(user(TEST_USR)) + .header(ADMIN_TEST_API_KEY_HEADER, ADMIN_TEST_API_KEY_HEADER_VALUE) + .header("Authorization", "test-okta")) + .andExpect(status().isUnauthorized()) + .andReturn(); + assertThat(result.getResponse().getStatus(), is(equalTo(401))); + } + @Test + public void testRetrieveAndUpdateCodeSystemsUnauthorized() throws Exception { + Principal principal = mock(Principal.class); + when(principal.getName()).thenReturn(TEST_USER); + when(vsacService.findByHarpId(anyString())).thenReturn(Optional.ofNullable(umlsUser)); + mockMvc + .perform( + MockMvcRequestBuilders.get("/terminology/update-code-systems") + .with(csrf()) + .with(user(TEST_USR)) + .header(ADMIN_TEST_API_KEY_HEADER, ADMIN_TEST_API_KEY_HEADER_VALUE) + .header("Authorization", "test-okta")) + .andExpect(status().isOk()); + } } diff --git a/src/test/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyControllerTest.java b/src/test/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyControllerTest.java index 7442f55..fa203d9 100644 --- a/src/test/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyControllerTest.java +++ b/src/test/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyControllerTest.java @@ -6,6 +6,7 @@ import gov.cms.madie.terminology.exceptions.VsacUnauthorizedException; import gov.cms.madie.terminology.models.CodeSystem; import gov.cms.madie.terminology.models.UmlsUser; +import gov.cms.madie.terminology.repositories.CodeSystemRepository; import gov.cms.madie.terminology.service.FhirTerminologyService; import gov.cms.madie.terminology.service.VsacService; import org.junit.jupiter.api.BeforeEach; @@ -16,6 +17,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockHttpServletRequest; import java.security.Principal; import java.time.Instant; @@ -26,26 +28,27 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class VsacFhirTerminologyControllerTest { + private CodeSystemRepository codeSystemRepository; @Mock private VsacService vsacService; - @Mock private FhirTerminologyService fhirTerminologyService; + @Mock FhirTerminologyService fhirTerminologyService; @InjectMocks private VsacFhirTerminologyController vsacFhirTerminologyController; private UmlsUser umlsUser; private static final String TEST_USER = "test.user"; - + private static final String ADMIN_TEST_API_KEY_HEADER = "api-key"; + private static final String ADMIN_TEST_API_KEY_HEADER_VALUE = "0a51991c"; private static final String TEST_HARP_ID = "te$tHarpId"; private static final String TEST_API_KEY = "te$tKey"; - + MockHttpServletRequest request; private final List mockManifests = new ArrayList<>(); - private final List mockQdmValueSets = new ArrayList<>(); + @BeforeEach public void setUp() { umlsUser = UmlsUser.builder().apiKey(TEST_API_KEY).harpId(TEST_HARP_ID).build(); @@ -66,6 +69,7 @@ public void setUp() { .version("20240101") .displayName("test-value-set-display-name") .build()); + request = new MockHttpServletRequest(); } @Test @@ -122,15 +126,13 @@ void retrieveAndUpdateCodeSystemsSuccessfully() { Principal principal = mock(Principal.class); when(principal.getName()).thenReturn(TEST_USER); when(vsacService.findByHarpId(anyString())).thenReturn(Optional.ofNullable(umlsUser)); - when(fhirTerminologyService.retrieveAllCodeSystems(any())).thenReturn(mockCodeSystemsPage); ResponseEntity> response = - vsacFhirTerminologyController.retrieveAndUpdateCodeSystems(principal); + vsacFhirTerminologyController.retrieveAndUpdateCodeSystems(principal, request, TEST_API_KEY, TEST_USER); assertEquals(response.getStatusCode(), HttpStatus.OK); assertEquals(response.getBody(), mockCodeSystemsPage); } - @Test void testUnAuthorizedUmlsUserWhileFetchingValueSetsExpansions() { Principal principal = mock(Principal.class); @@ -149,6 +151,6 @@ void testUnAuthorizedUmlsUserWhileretrievingAndUpdatingCodeSystems() { .thenReturn(Optional.ofNullable(UmlsUser.builder().build())); assertThrows( VsacUnauthorizedException.class, - () -> vsacFhirTerminologyController.retrieveAndUpdateCodeSystems(principal)); + () -> vsacFhirTerminologyController.retrieveAndUpdateCodeSystems(principal, request, TEST_API_KEY, TEST_USER)); } } diff --git a/src/test/java/gov/cms/madie/terminology/service/FhirTerminologyServiceTest.java b/src/test/java/gov/cms/madie/terminology/service/FhirTerminologyServiceTest.java index bdb330c..adfe86f 100644 --- a/src/test/java/gov/cms/madie/terminology/service/FhirTerminologyServiceTest.java +++ b/src/test/java/gov/cms/madie/terminology/service/FhirTerminologyServiceTest.java @@ -1,7 +1,6 @@ package gov.cms.madie.terminology.service; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.parser.IParser; import com.okta.commons.lang.Collections; import gov.cms.madie.models.mapping.CodeSystemEntry; import gov.cms.madie.models.measure.ManifestExpansion; @@ -22,7 +21,6 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.Mockito; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.times; From 471ae50d8f4484102e29a50b826a3ee8947dc746 Mon Sep 17 00:00:00 2001 From: mcmcphillips Date: Fri, 29 Mar 2024 08:49:39 -0700 Subject: [PATCH 11/14] MAT-6916: Add same reference as measure-service. Not certain of use --- src/main/resources/application.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 29eb5b0..c3f5d0f 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -18,6 +18,8 @@ okta: issuer: ${OKTA_ISSUER:https://dev-18092578.okta.com/oauth2/default} audience: ${OKTA_AUDIENCE:api://default} +admin-api-key: ${ADMIN_API_KEY:0a51991c} + client: vsac_base_url: https://vsac.nlm.nih.gov/vsac valueset_endpoint: /svs/RetrieveMultipleValueSets?id={oid}&profile={profile}&includeDraft={includeDraft} From 2fc80b62097279508e35f3d47a1dd65e858c4604 Mon Sep 17 00:00:00 2001 From: mcmcphillips Date: Fri, 29 Mar 2024 09:46:53 -0700 Subject: [PATCH 12/14] MAT-6916: modfiy code style, change identifier get, rename to oid --- .../madie/terminology/models/CodeSystem.java | 2 +- .../service/FhirTerminologyService.java | 128 +++++++++--------- src/main/resources/application.yml | 6 +- .../VsacFhirTerminologyControllerTest.java | 2 +- 4 files changed, 67 insertions(+), 71 deletions(-) diff --git a/src/main/java/gov/cms/madie/terminology/models/CodeSystem.java b/src/main/java/gov/cms/madie/terminology/models/CodeSystem.java index 2fd31ab..05172d4 100644 --- a/src/main/java/gov/cms/madie/terminology/models/CodeSystem.java +++ b/src/main/java/gov/cms/madie/terminology/models/CodeSystem.java @@ -22,7 +22,7 @@ public class CodeSystem { private String name; private String version; private String versionId; - private String value; // identifier[0].value oid of identifier List + private String oid; // identifier[0].value oid of identifier List private Instant lastUpdated; // when queried private Date lastUpdatedUpstream; // when was resource last updated on vsac end } diff --git a/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java b/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java index ab7baad..920f649 100644 --- a/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java +++ b/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java @@ -32,6 +32,7 @@ public class FhirTerminologyService { private final FhirTerminologyServiceWebClient fhirTerminologyServiceWebClient; private final MappingService mappingService; private final CodeSystemRepository codeSystemRepository; + @Cacheable("manifest-list") public List getManifests(UmlsUser umlsUser) { IParser parser = fhirContext.newJsonParser(); @@ -113,74 +114,7 @@ private List getValueSetConcepts( return List.of(); } - // one to call only, one to mutate and build - private Bundle retrieveCodeSystemsPage(UmlsUser umlsUser, Integer offset, Integer count) { - IParser parser = fhirContext.newJsonParser(); - String responseString = - fhirTerminologyServiceWebClient.getCodeSystemsPage(offset, count, umlsUser.getApiKey()); - return parser.parseResource( - Bundle.class, responseString); - } - - private void updateOrInsertAllCodeSystems(List codeSystemList){ - for (CodeSystem codeSystem : codeSystemList) { - var id = codeSystem.getTitle() + codeSystem.getVersion(); - Optional existingCodeSystemOptional = codeSystemRepository.findById(id); - if (existingCodeSystemOptional.isPresent()) { - // Update existing CodeSystem - CodeSystem existingCodeSystem = existingCodeSystemOptional.get(); - existingCodeSystem.setTitle(codeSystem.getTitle()); - existingCodeSystem.setName(codeSystem.getName()); - existingCodeSystem.setVersion(codeSystem.getVersion()); - existingCodeSystem.setValue(codeSystem.getValue()); - existingCodeSystem.setLastUpdated(Instant.now()); - existingCodeSystem.setLastUpdatedUpstream(codeSystem.getLastUpdatedUpstream()); - codeSystemRepository.save(existingCodeSystem); - log.info("CodeSystem updated: {}", existingCodeSystem); - } else { - // Insert new CodeSystem - codeSystemRepository.save(codeSystem); - log.info("New CodeSystem inserted: {}", codeSystem); - } - } - } - private void recursiveRetrieveCodeSystems(UmlsUser umlsUser, Integer offset, Integer count, List allCodeSystems) { - log.info("requesting page offset: {} count: {}", offset, count); - Bundle codeSystemBundle = retrieveCodeSystemsPage(umlsUser, offset, count); - List codeSystemsPage = new ArrayList<>(); // build small list - codeSystemBundle.getEntry().forEach(entry -> { - var codeSystem = (org.hl7.fhir.r4.model.CodeSystem) entry.getResource(); - String codeSystemValue = ""; - if (!codeSystem.getIdentifier().isEmpty()) { - codeSystemValue = codeSystem.getIdentifier().get(0).getValue(); - } - codeSystemsPage.add( - CodeSystem.builder() - .id(codeSystem.getTitle() + codeSystem.getVersion()) - .title(codeSystem.getTitle()) - .name(codeSystem.getName()) - .version(codeSystem.getVersion()) - .versionId(codeSystem.getMeta().getVersionId()) - .value(codeSystemValue) - .lastUpdated(Instant.now()) - .lastUpdatedUpstream(codeSystem.getMeta().getLastUpdated()) - .build()); - }); - allCodeSystems.addAll(codeSystemsPage); // update big list - var links = codeSystemBundle.getLink(); - links.forEach((l) -> { - if (l.getRelation().equals("next")){ - // if next, call self and continue until fail. - UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(l.getUrl()); - String newOffset = builder.build().getQueryParams().getFirst("_offset"); - String newCount = builder.build().getQueryParams().getFirst("_count"); - assert newOffset != null; - assert newCount != null; - recursiveRetrieveCodeSystems(umlsUser, Integer.parseInt(newOffset), Integer.parseInt(newCount), allCodeSystems); - } - }); - } public List retrieveAllCodeSystems(UmlsUser umlsUser) { List allCodeSystems = new ArrayList<>(); @@ -189,4 +123,64 @@ public List retrieveAllCodeSystems(UmlsUser umlsUser) { updateOrInsertAllCodeSystems(allCodeSystems); return allCodeSystems; } + private void recursiveRetrieveCodeSystems(UmlsUser umlsUser, Integer offset, Integer count, List allCodeSystems) { + log.info("requesting page offset: {} count: {}", offset, count); + Bundle codeSystemBundle = retrieveCodeSystemsPage(umlsUser, offset, count); + List codeSystemsPage = new ArrayList<>(); // build small list + codeSystemBundle.getEntry().forEach(entry -> { + var codeSystem = (org.hl7.fhir.r4.model.CodeSystem) entry.getResource(); + String codeSystemValue = ""; + for (org.hl7.fhir.r4.model.Identifier identifier : codeSystem.getIdentifier()) { + if (identifier.getValue() != null && !identifier.getValue().isEmpty()) { + codeSystemValue = identifier.getValue(); + break; + } + } + codeSystemsPage.add( + CodeSystem.builder() + .id(codeSystem.getTitle() + codeSystem.getVersion()) + .title(codeSystem.getTitle()) + .name(codeSystem.getName()) + .version(codeSystem.getVersion()) + .versionId(codeSystem.getMeta().getVersionId()) + .oid(codeSystemValue) + .lastUpdated(Instant.now()) + .lastUpdatedUpstream(codeSystem.getMeta().getLastUpdated()) + .build()); + }); + allCodeSystems.addAll(codeSystemsPage); // update big list + var links = codeSystemBundle.getLink(); + links.forEach((l) -> { + if (l.getRelation().equals("next")){ + // if next, call self and continue until fail. + UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(l.getUrl()); + String newOffset = builder.build().getQueryParams().getFirst("_offset"); + String newCount = builder.build().getQueryParams().getFirst("_count"); + assert newOffset != null; + assert newCount != null; + recursiveRetrieveCodeSystems(umlsUser, Integer.parseInt(newOffset), Integer.parseInt(newCount), allCodeSystems); + } + }); + } + // one to call only, one to mutate and build + private Bundle retrieveCodeSystemsPage(UmlsUser umlsUser, Integer offset, Integer count) { + IParser parser = fhirContext.newJsonParser(); + String responseString = + fhirTerminologyServiceWebClient.getCodeSystemsPage(offset, count, umlsUser.getApiKey()); + return parser.parseResource( + Bundle.class, responseString); + } + + private void updateOrInsertAllCodeSystems(List codeSystemList){ + for (CodeSystem codeSystem : codeSystemList) { + var id = codeSystem.getTitle() + codeSystem.getVersion(); + Optional existingCodeSystemOptional = codeSystemRepository.findById(id); + if (existingCodeSystemOptional.isEmpty()) { + // Insert new CodeSystem + codeSystemRepository.save(codeSystem); + log.info("New CodeSystem inserted: {}", codeSystem); + } + } + } + } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index c3f5d0f..92ce74d 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -33,11 +33,13 @@ spring: session: store-type: none codec: - max-in-memory-size: 24MB + max-in-memory-size: 500KB data: mongodb: uri: ${MONGO_URI:mongodb://${DBUSER:root}:${DBPASS:E5press0}@localhost:27017/terminology}?authSource=admin&maxPoolSize=50&connectTimeoutMS=2000&serverSelectionTimeoutMS=2000 mapping: data: - code-system-entry-url: ${CODE_SYSTEM_ENTRY_URL:https://madie-dev-static.s3.amazonaws.com/mappings/code-system-entry.json} + # code-system-entry-url: ${CODE_SYSTEM_ENTRY_URL:https://madie-dev-static.s3.amazonaws.com/mappings/code-system-entry.json} + code-system-entry-url: ${CODE_SYSTEM_ENTRY_URL:file:////Users/43161/madie/support-data/prod/madie/code-system-entry.json} + # ${CODE_SYSTEM_ENTRY_URL:file:///Users/ashok.dongare/Documents/projects/madie/services/code-system-entry.json} diff --git a/src/test/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyControllerTest.java b/src/test/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyControllerTest.java index fa203d9..fec8698 100644 --- a/src/test/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyControllerTest.java +++ b/src/test/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyControllerTest.java @@ -120,7 +120,7 @@ void retrieveAndUpdateCodeSystemsSuccessfully() { .name("name") .version("version") .versionId("vid") - .value("urlval") + .oid("urlval") .lastUpdated(Instant.now()) .build()); Principal principal = mock(Principal.class); From bcafc92e70ef8593c12b4423bd7264d904d3e9e0 Mon Sep 17 00:00:00 2001 From: mcmcphillips Date: Fri, 29 Mar 2024 10:04:26 -0700 Subject: [PATCH 13/14] MAT-6916: revert application, remove unused config --- .../terminology/config/WebFluxConfiguration.java | 13 ------------- src/main/resources/application.yml | 6 ++---- 2 files changed, 2 insertions(+), 17 deletions(-) delete mode 100644 src/main/java/gov/cms/madie/terminology/config/WebFluxConfiguration.java diff --git a/src/main/java/gov/cms/madie/terminology/config/WebFluxConfiguration.java b/src/main/java/gov/cms/madie/terminology/config/WebFluxConfiguration.java deleted file mode 100644 index 58e8853..0000000 --- a/src/main/java/gov/cms/madie/terminology/config/WebFluxConfiguration.java +++ /dev/null @@ -1,13 +0,0 @@ -package gov.cms.madie.terminology.config; - -import org.springframework.context.annotation.Configuration; -import org.springframework.http.codec.ServerCodecConfigurer; -import org.springframework.web.reactive.config.WebFluxConfigurer; - -@Configuration -public class WebFluxConfiguration implements WebFluxConfigurer { - @Override - public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) { - configurer.defaultCodecs().maxInMemorySize(500 * 1024); - } -} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 92ce74d..76d6b9b 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -33,13 +33,11 @@ spring: session: store-type: none codec: - max-in-memory-size: 500KB + max-in-memory-size: 24MB data: mongodb: uri: ${MONGO_URI:mongodb://${DBUSER:root}:${DBPASS:E5press0}@localhost:27017/terminology}?authSource=admin&maxPoolSize=50&connectTimeoutMS=2000&serverSelectionTimeoutMS=2000 mapping: data: - # code-system-entry-url: ${CODE_SYSTEM_ENTRY_URL:https://madie-dev-static.s3.amazonaws.com/mappings/code-system-entry.json} - code-system-entry-url: ${CODE_SYSTEM_ENTRY_URL:file:////Users/43161/madie/support-data/prod/madie/code-system-entry.json} - # ${CODE_SYSTEM_ENTRY_URL:file:///Users/ashok.dongare/Documents/projects/madie/services/code-system-entry.json} + code-system-entry-url: ${CODE_SYSTEM_ENTRY_URL:https://madie-dev-static.s3.amazonaws.com/mappings/code-system-entry.json} \ No newline at end of file From b92a5bbe381c97d213f35baf08b3849c87794f8f Mon Sep 17 00:00:00 2001 From: mcmcphillips Date: Fri, 29 Mar 2024 12:31:43 -0700 Subject: [PATCH 14/14] MAT-6916: remove /res from constant as it's non standard. --- src/main/resources/application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 76d6b9b..4f76819 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -27,7 +27,7 @@ client: fhir-terminology-service: base-url: ${FHIR_TERMINOLOGY_BASE_URL:https://uat-cts.nlm.nih.gov/fhir} manifests-urn: /Library - code-system-urn: /res/CodeSystem + code-system-urn: /CodeSystem spring: session: