Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@
<dependency>
<groupId>gov.cms.madie</groupId>
<artifactId>madie-java-models</artifactId>
<version>0.10.13-SNAPSHOT</version>
<version>0.10.15-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package gov.cms.madie.madiefhirservice.services;

import gov.cms.madie.madiefhirservice.exceptions.CqlLibraryNotFoundException;
import gov.cms.madie.models.library.CqlLibrary;
import gov.cms.madie.models.dto.CqlLibraryDto;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.cqframework.cql.cql2elm.CqlCompilerException;
Expand All @@ -17,6 +17,7 @@
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;
import java.util.Optional;

@Service
@Slf4j
Expand All @@ -31,18 +32,19 @@ public class CqlLibraryService {
@Value("${madie.library.service.versioned.uri}")
private String librariesVersionedUri;

@Cacheable(value = "libraries", key = "{ #root.methodName, #name, #version }")
public CqlLibrary getLibrary(
@Cacheable(value = "libraries", key = "{ #root.methodName, #name, #version, #namespacePrefix }")
public CqlLibraryDto getLibrary(
String name,
String version,
Optional<String> namespacePrefix,
String accessToken,
CqlCompilerException.ErrorSeverity errorSeverity) {
URI uri = buildMadieLibraryServiceUri(name, version, errorSeverity);
URI uri = buildMadieLibraryServiceUri(name, version, namespacePrefix, errorSeverity);
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", accessToken);

ResponseEntity<CqlLibrary> responseEntity =
restTemplate.exchange(uri, HttpMethod.GET, new HttpEntity<>(headers), CqlLibrary.class);
ResponseEntity<CqlLibraryDto> responseEntity =
restTemplate.exchange(uri, HttpMethod.GET, new HttpEntity<>(headers), CqlLibraryDto.class);

if (responseEntity.getStatusCode().is2xxSuccessful()) {
if (responseEntity.hasBody()) {
Expand Down Expand Up @@ -78,11 +80,15 @@ public CqlLibrary getLibrary(
* @return
*/
private URI buildMadieLibraryServiceUri(
String name, String version, CqlCompilerException.ErrorSeverity errorSeverity) {
String name,
String version,
Optional<String> namespacePrefix,
CqlCompilerException.ErrorSeverity errorSeverity) {
return UriComponentsBuilder.fromUriString(madieLibraryService + librariesVersionedUri)
.queryParam("name", name)
.queryParam("version", version)
.queryParam("errorSeverity", errorSeverity)
.queryParamIfPresent("namespacePrefix", namespacePrefix)
.build()
.encode()
.toUri();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

import gov.cms.madie.madiefhirservice.cql.LibraryCqlVisitor;
import gov.cms.madie.madiefhirservice.cql.LibraryCqlVisitorFactory;
import gov.cms.madie.models.dto.CqlLibraryDto;
import gov.cms.madie.madiefhirservice.exceptions.LibraryAttachmentNotFoundException;
import gov.cms.madie.madiefhirservice.utils.BundleUtil;
import gov.cms.madie.models.library.CqlLibrary;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
Expand All @@ -17,6 +17,7 @@
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.Optional;

@Service
@Slf4j
Expand All @@ -29,7 +30,7 @@ public class LibraryService {
private final HumanReadableService humanReadableService;

public Library cqlLibraryToFhirLibrary(
CqlLibrary cqlLibrary, final String bundleType, String accessToken) {
CqlLibraryDto cqlLibrary, final String bundleType, String accessToken) {
Library library = libraryTranslatorService.convertToFhirLibrary(cqlLibrary, null, accessToken);
if (BundleUtil.MEASURE_BUNDLE_TYPE_EXPORT.equals(bundleType)) {
library.setText(createLibraryNarrativeText(library));
Expand All @@ -52,10 +53,12 @@ public void getIncludedLibraries(
for (Pair<String, String> libraryNameValuePair : visitor.getIncludedLibraries()) {
String key = libraryNameValuePair.getLeft() + libraryNameValuePair.getRight();
if (!libraryMap.containsKey(key)) {
CqlLibrary cqlLibrary =
var libraryParts = parseLibraryString(libraryNameValuePair.getLeft());
CqlLibraryDto cqlLibrary =
cqlLibraryService.getLibrary(
libraryNameValuePair.getLeft(),
libraryNameValuePair.getRight(),
libraryParts[1], // name
libraryNameValuePair.getRight(), // version
Optional.ofNullable(libraryParts[0]), // prefix
accessToken,
errorSeverity);
// Todo If the library is already in libraryMap, we can skip the call to
Expand All @@ -72,6 +75,38 @@ public void getIncludedLibraries(
}
}

/**
* Parses a library identifier into namespace and library name parts.
*
* <p>The split is performed on the last {@code '.'} character:
*
* <ul>
* <li>If {@code fullLibraryString} is {@code null} or blank, returns {@code {"", ""}}.
* <li>If no {@code '.'} is present, returns {@code {"", trimmedInput}}.
* <li>If {@code '.'} is present, returns {@code {trimmedNamespace, trimmedLibraryName}}.
* </ul>
*
* @param fullLibraryString the raw library identifier, optionally namespace-qualified
* @return a two-element array where index {@code 0} is the namespace (or empty string) and index
* {@code 1} is the library name (or empty string)
*/
public String[] parseLibraryString(String fullLibraryString) {
if (fullLibraryString == null || fullLibraryString.trim().isEmpty()) {
return new String[] {"", ""};
}

int lastDotIndex = fullLibraryString.lastIndexOf('.');

if (lastDotIndex == -1) {
return new String[] {"", fullLibraryString.trim()};
}

String namespace = fullLibraryString.substring(0, lastDotIndex).trim();
String libraryName = fullLibraryString.substring(lastDotIndex + 1).trim();

return new String[] {namespace, libraryName};
}

private Narrative createLibraryNarrativeText(Library library) {
Narrative narrative = new Narrative();
narrative.setStatus(NarrativeStatus.EXTENSIONS);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import gov.cms.madie.madiefhirservice.constants.UriConstants;
import gov.cms.madie.madiefhirservice.cql.LibraryCqlVisitorFactory;
import gov.cms.madie.madiefhirservice.dto.CqlLibraryDetails;
import gov.cms.madie.models.dto.CqlLibraryDto;
import gov.cms.madie.madiefhirservice.utils.FhirResourceHelpers;
import gov.cms.madie.models.library.CqlLibrary;
import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -42,40 +43,48 @@ public LibraryTranslatorService(

public Library convertToFhirLibrary(
CqlLibrary cqlLibrary, Set<String> expressions, String accessToken) {
var visitor = libCqlVisitorFactory.visit(cqlLibrary.getCql());
return convertToFhirLibrary(LibrarySource.from(cqlLibrary), expressions, accessToken);
}

public Library convertToFhirLibrary(
CqlLibraryDto cqlLibrary, Set<String> expressions, String accessToken) {
return convertToFhirLibrary(LibrarySource.from(cqlLibrary), expressions, accessToken);
}

private Library convertToFhirLibrary(
LibrarySource cqlLibrary, Set<String> expressions, String accessToken) {
var visitor = libCqlVisitorFactory.visit(cqlLibrary.cql());
Library library = new Library();
library.setId(cqlLibrary.getCqlLibraryName());
library.setId(cqlLibrary.name());
library.setLanguage("en");
library.setName(cqlLibrary.getCqlLibraryName());
library.setVersion(cqlLibrary.getVersion().toString());
library.setName(cqlLibrary.name());
library.setVersion(cqlLibrary.version());
library.setDate(new Date());
library.setStatus(Enumerations.PublicationStatus.ACTIVE);
library.setPublisher(
cqlLibrary.getPublisher() != null && StringUtils.isNotBlank(cqlLibrary.getPublisher())
? cqlLibrary.getPublisher()
cqlLibrary.publisher() != null && StringUtils.isNotBlank(cqlLibrary.publisher())
? cqlLibrary.publisher()
: UNKNOWN_VALUE);
library.setDescription(Objects.toString(cqlLibrary.getDescription(), UNKNOWN_VALUE));
library.setExperimental(cqlLibrary.isExperimental());
library.setContent(
createContent(cqlLibrary.getCql(), cqlLibrary.getElmJson(), cqlLibrary.getElmXml()));
library.setDescription(Objects.toString(cqlLibrary.description(), UNKNOWN_VALUE));
library.setExperimental(cqlLibrary.experimental());
library.setContent(createContent(cqlLibrary.cql(), cqlLibrary.elmJson(), cqlLibrary.elmXml()));
library.setType(createType(UriConstants.CodeSystem.LIBRARY_SYSTEM_TYPE_URI, SYSTEM_CODE));
library.setUrl(
FhirResourceHelpers.buildResourceFullUrl("Library", cqlLibrary.getCqlLibraryName()));
library.setUrl(FhirResourceHelpers.buildResourceFullUrl("Library", cqlLibrary.name()));
library.getExtension().addAll(visitor.getDrcExtensions());
library.setMeta(createLibraryMeta());
library.setTitle(cqlLibrary.getCqlLibraryName());
library.setPublisher(cqlLibrary.getPublisher());
library.setTitle(cqlLibrary.name());
library.setPublisher(cqlLibrary.publisher());
Identifier identifier = new Identifier();
identifier.setUse(IdentifierUse.OFFICIAL);
identifier.setSystem("https://madie.cms.gov/login");
identifier.setValue(cqlLibrary.getId());
identifier.setValue(cqlLibrary.id());
library.setIdentifier(List.of(identifier));
// Use the DataRequirementsProcessor to construct data requirements and related artifacts.
Library libraryModuleDefinition =
retrieveLibraryModuleDefinition(
CqlLibraryDetails.builder()
.libraryName(cqlLibrary.getCqlLibraryName())
.cql(cqlLibrary.getCql())
.libraryName(cqlLibrary.name())
.cql(cqlLibrary.cql())
.expressions(expressions)
.build(),
accessToken);
Expand All @@ -84,6 +93,44 @@ public Library convertToFhirLibrary(
return library;
}

private record LibrarySource(
String id,
String name,
String version,
String cql,
String elmJson,
String elmXml,
String publisher,
String description,
boolean experimental) {

private static LibrarySource from(CqlLibrary cqlLibrary) {
return new LibrarySource(
cqlLibrary.getId(),
cqlLibrary.getCqlLibraryName(),
cqlLibrary.getVersion().toString(),
cqlLibrary.getCql(),
cqlLibrary.getElmJson(),
cqlLibrary.getElmXml(),
cqlLibrary.getPublisher(),
cqlLibrary.getDescription(),
cqlLibrary.isExperimental());
}

private static LibrarySource from(CqlLibraryDto cqlLibrary) {
return new LibrarySource(
cqlLibrary.getId(),
cqlLibrary.getCqlLibraryName(),
cqlLibrary.getVersion(),
cqlLibrary.getCql(),
cqlLibrary.getElmJson(),
cqlLibrary.getElmXml(),
cqlLibrary.getPublisher(),
cqlLibrary.getDescription(),
cqlLibrary.isExperimental());
}
}

private Library retrieveLibraryModuleDefinition(
CqlLibraryDetails cqlLibraryDetails, String accessToken) {
org.hl7.fhir.r5.model.Library r5moduleDefinition =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package gov.cms.madie.madiefhirservice.services;

import gov.cms.madie.madiefhirservice.exceptions.CqlLibraryNotFoundException;
import gov.cms.madie.models.common.Version;
import gov.cms.madie.models.library.CqlLibrary;
import gov.cms.madie.models.dto.CqlLibraryDto;
import org.cqframework.cql.cql2elm.CqlCompilerException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand All @@ -18,6 +17,7 @@
import org.springframework.web.client.RestTemplate;

import java.net.URI;
import java.util.Optional;

import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
Expand All @@ -43,18 +43,19 @@ void setup() {

@Test
void getLibraryReturnsLibrary() {
CqlLibrary theLibrary =
CqlLibrary.builder()
.cqlLibraryName("FHIRHelpers")
.version(Version.parse("4.0.001"))
.build();
ResponseEntity<CqlLibrary> response = ResponseEntity.ok(theLibrary);
CqlLibraryDto theLibrary =
CqlLibraryDto.builder().cqlLibraryName("FHIRHelpers").version("4.0.001").build();
ResponseEntity<CqlLibraryDto> response = ResponseEntity.ok(theLibrary);
when(restTemplate.exchange(
any(URI.class), any(HttpMethod.class), any(HttpEntity.class), any(Class.class)))
.thenReturn(response);
CqlLibrary output =
CqlLibraryDto output =
cqlLibraryService.getLibrary(
"FHIRHelpers", "4.0.001", "OKTA_TOKEN", CqlCompilerException.ErrorSeverity.Info);
"FHIRHelpers",
"4.0.001",
Optional.empty(),
"OKTA_TOKEN",
CqlCompilerException.ErrorSeverity.Info);
assertThat(output, is(notNullValue()));
assertThat(output, is(equalTo(theLibrary)));
}
Expand All @@ -72,6 +73,7 @@ void getLibraryReturnsExceptionForLibraryNotFound() {
cqlLibraryService.getLibrary(
"FHIRHelpers",
"4.0.001",
Optional.empty(),
"OKTA_TOKEN",
CqlCompilerException.ErrorSeverity.Info));
assertThat(
Expand All @@ -85,9 +87,13 @@ void getLibraryReturnsNullForConflict() {
when(restTemplate.exchange(
any(URI.class), any(HttpMethod.class), any(HttpEntity.class), any(Class.class)))
.thenReturn(response);
CqlLibrary output =
CqlLibraryDto output =
cqlLibraryService.getLibrary(
"FHIRHelpers", "4.0.001", "OKTA_TOKEN", CqlCompilerException.ErrorSeverity.Info);
"FHIRHelpers",
"4.0.001",
Optional.empty(),
"OKTA_TOKEN",
CqlCompilerException.ErrorSeverity.Info);
assertThat(output, is(nullValue()));
}

Expand All @@ -97,9 +103,13 @@ void getLibraryReturnsNullForOkNoBody() {
when(restTemplate.exchange(
any(URI.class), any(HttpMethod.class), any(HttpEntity.class), any(Class.class)))
.thenReturn(response);
CqlLibrary output =
CqlLibraryDto output =
cqlLibraryService.getLibrary(
"FHIRHelpers", "4.0.001", "OKTA_TOKEN", CqlCompilerException.ErrorSeverity.Info);
"FHIRHelpers",
"4.0.001",
Optional.empty(),
"OKTA_TOKEN",
CqlCompilerException.ErrorSeverity.Info);
assertThat(output, is(nullValue()));
}
}
Loading
Loading