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
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package gov.cms.madie.madiefhirservice.constants;

public final class LibraryContentTypeConstants {
public static final String CQL = "text/cql";
public static final String ELM_JSON = "application/elm+json";
public static final String ELM_XML = "application/elm+xml";

private LibraryContentTypeConstants() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package gov.cms.madie.madiefhirservice.services;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.DataFormatException;
import gov.cms.madie.madiefhirservice.constants.LibraryContentTypeConstants;
import gov.cms.madie.models.dto.CqlLibraryDto;
import java.nio.charset.StandardCharsets;
import org.hl7.fhir.r4.model.Attachment;
import org.hl7.fhir.r4.model.Library;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
public class ExternalLibraryResourceMapper {
private final FhirContext qicoreFhirContext;

public ExternalLibraryResourceMapper(
@Qualifier("qicoreFhirContext") FhirContext qicoreFhirContext) {
this.qicoreFhirContext = qicoreFhirContext;
}

public Library toFhirLibrary(CqlLibraryDto cqlLibrary) {
Library library = parseExternalFhirLibrary(cqlLibrary);
restoreContent(library, cqlLibrary);
return library;
}

private Library parseExternalFhirLibrary(CqlLibraryDto cqlLibrary) {
try {
return qicoreFhirContext
.newJsonParser()
.parseResource(Library.class, cqlLibrary.getFhirResource());
} catch (DataFormatException ex) {
throw new DataFormatException(
"Unable to parse external FHIR Library [" + cqlLibrary.getCqlLibraryName() + "]", ex);
}
}

private void restoreContent(Library library, CqlLibraryDto cqlLibrary) {
restoreAttachment(library, LibraryContentTypeConstants.CQL, cqlLibrary.getCql());
restoreAttachment(library, LibraryContentTypeConstants.ELM_XML, cqlLibrary.getElmXml());
restoreAttachment(library, LibraryContentTypeConstants.ELM_JSON, cqlLibrary.getElmJson());
}

private void restoreAttachment(Library library, String contentType, String content) {
if (content == null) {
return;
}
Attachment attachment =
library.getContent().stream()
.filter(existing -> contentType.equals(existing.getContentType()))
.findFirst()
.orElseGet(() -> library.addContent().setContentType(contentType));
attachment.setData(content.getBytes(StandardCharsets.UTF_8));
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package gov.cms.madie.madiefhirservice.services;

import gov.cms.madie.madiefhirservice.constants.LibraryContentTypeConstants;
import gov.cms.madie.madiefhirservice.constants.UriConstants;
import gov.cms.madie.madiefhirservice.cql.LibraryCqlVisitorFactory;
import gov.cms.madie.madiefhirservice.dto.CqlLibraryDetails;
Expand All @@ -26,19 +27,20 @@
@Slf4j
@Service
public class LibraryTranslatorService {
public static final String CQL_CONTENT_TYPE = "text/cql";
public static final String JSON_ELM_CONTENT_TYPE = "application/elm+json";
public static final String XML_ELM_CONTENT_TYPE = "application/elm+xml";
public static final String SYSTEM_CODE = "logic-library";
public static final String UNKNOWN_VALUE = "UNKNOWN";

private final LibraryCqlVisitorFactory libCqlVisitorFactory;
private final ElmTranslatorClient elmTranslatorClient;
private final ExternalLibraryResourceMapper externalLibraryResourceMapper;

public LibraryTranslatorService(
LibraryCqlVisitorFactory libCqlVisitorFactory, ElmTranslatorClient elmTranslatorClient) {
LibraryCqlVisitorFactory libCqlVisitorFactory,
ElmTranslatorClient elmTranslatorClient,
ExternalLibraryResourceMapper externalLibraryResourceMapper) {
this.libCqlVisitorFactory = libCqlVisitorFactory;
this.elmTranslatorClient = elmTranslatorClient;
this.externalLibraryResourceMapper = externalLibraryResourceMapper;
}

public Library convertToFhirLibrary(
Expand All @@ -48,9 +50,16 @@ public Library convertToFhirLibrary(

public Library convertToFhirLibrary(
CqlLibraryDto cqlLibrary, Set<String> expressions, String accessToken) {
if (cqlLibrary.isExternal() && StringUtils.isNotBlank(cqlLibrary.getFhirResource())) {
return convertExternalFhirLibrary(cqlLibrary);
}
return convertToFhirLibrary(LibrarySource.from(cqlLibrary), expressions, accessToken);
}

private Library convertExternalFhirLibrary(CqlLibraryDto cqlLibrary) {
return externalLibraryResourceMapper.toFhirLibrary(cqlLibrary);
}

private Library convertToFhirLibrary(
LibrarySource cqlLibrary, Set<String> expressions, String accessToken) {
var visitor = libCqlVisitorFactory.visit(cqlLibrary.cql());
Expand Down Expand Up @@ -163,13 +172,13 @@ private Meta createLibraryMeta() {
private List<Attachment> createContent(String cql, String elmJson, String elmXml) {
List<Attachment> attachments = new ArrayList<>(3);
if (cql != null) {
attachments.add(createAttachment(CQL_CONTENT_TYPE, cql.getBytes()));
attachments.add(createAttachment(LibraryContentTypeConstants.CQL, cql.getBytes()));
}
if (elmXml != null) {
attachments.add(createAttachment(XML_ELM_CONTENT_TYPE, elmXml.getBytes()));
attachments.add(createAttachment(LibraryContentTypeConstants.ELM_XML, elmXml.getBytes()));
}
if (elmJson != null) {
attachments.add(createAttachment(JSON_ELM_CONTENT_TYPE, elmJson.getBytes()));
attachments.add(createAttachment(LibraryContentTypeConstants.ELM_JSON, elmJson.getBytes()));
}
return attachments;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package gov.cms.madie.madiefhirservice.services;

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.DataFormatException;
import gov.cms.madie.models.dto.CqlLibraryDto;
import java.nio.charset.StandardCharsets;
import org.hl7.fhir.r4.model.Attachment;
import org.hl7.fhir.r4.model.Library;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class ExternalLibraryResourceMapperTest {
private ExternalLibraryResourceMapper externalLibraryResourceMapper;

@BeforeEach
void setUp() {
externalLibraryResourceMapper = new ExternalLibraryResourceMapper(FhirContext.forR4Cached());
}

@Test
void toFhirLibraryShouldPreserveSourceMetadataAndRestoreContent() {
// given
CqlLibraryDto cqlLibrary =
CqlLibraryDto.builder()
.cqlLibraryName("ExternalLibrary")
.cql("library ExternalLibrary version '1.0.1'")
.elmJson("ELM JSON")
.elmXml("ELM XML")
.fhirResource(
"""
{
"resourceType": "Library",
"id": "source-library-id",
"url": "http://example.org/Library/ExternalLibrary",
"title": "Source Library Title",
"status": "active",
"content": [
{"contentType": "text/cql", "title": "Source CQL"},
{"contentType": "application/elm+json", "title": "Source ELM JSON"}
]
}
""")
.build();

// when
Library library = externalLibraryResourceMapper.toFhirLibrary(cqlLibrary);

// then
assertThat(library.getIdElement().getIdPart(), is(equalTo("source-library-id")));
assertThat(library.getUrl(), is(equalTo("http://example.org/Library/ExternalLibrary")));
assertThat(library.getTitle(), is(equalTo("Source Library Title")));
assertThat(getContent(library, "text/cql").getTitle(), is(equalTo("Source CQL")));
assertThat(
getContentData(library, "text/cql"),
is(equalTo("library ExternalLibrary version '1.0.1'")));
assertThat(getContentData(library, "application/elm+json"), is(equalTo("ELM JSON")));
assertThat(getContentData(library, "application/elm+xml"), is(equalTo("ELM XML")));
}

@Test
void toFhirLibraryShouldRejectMalformedResource() {
// given
CqlLibraryDto cqlLibrary =
CqlLibraryDto.builder()
.cqlLibraryName("MalformedLibrary")
.fhirResource("{not-json")
.build();

// when
DataFormatException exception =
assertThrows(
DataFormatException.class,
() -> externalLibraryResourceMapper.toFhirLibrary(cqlLibrary));

// then
assertThat(exception.getMessage(), containsString("MalformedLibrary"));
}

@Test
void toFhirLibraryShouldRejectWrongResourceType() {
// given
CqlLibraryDto cqlLibrary =
CqlLibraryDto.builder()
.cqlLibraryName("ObservationResource")
.fhirResource("{\"resourceType\":\"Observation\",\"status\":\"final\",\"code\":{}}")
.build();

// when
DataFormatException exception =
assertThrows(
DataFormatException.class,
() -> externalLibraryResourceMapper.toFhirLibrary(cqlLibrary));

// then
assertThat(exception.getMessage(), containsString("ObservationResource"));
}

private String getContentData(Library library, String contentType) {
return new String(getContent(library, contentType).getData(), StandardCharsets.UTF_8);
}

private Attachment getContent(Library library, String contentType) {
return library.getContent().stream()
.filter(content -> contentType.equals(content.getContentType()))
.findFirst()
.orElseThrow();
}
}
Loading
Loading