Skip to content

Commit

Permalink
Merge pull request #119 from MeasureAuthoringTool/feature/MAT-5404-re…
Browse files Browse the repository at this point in the history
…move-HAPI-for-libraries

replace HAPI FHIR with CQL Library Service for fetching CQL Libraries
  • Loading branch information
nmorasb authored Apr 25, 2023
2 parents 90a06f8 + f4baf19 commit 5d20953
Show file tree
Hide file tree
Showing 11 changed files with 203 additions and 439 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package gov.cms.madie.madiefhirservice.exceptions;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(value = HttpStatus.NOT_FOUND)
@Slf4j
public class CqlLibraryNotFoundException extends RuntimeException {

private static final String NOT_FOUND_BY_FIND_DATA =
"Cannot find a CQL Library with name: %s, version: %s";

public CqlLibraryNotFoundException(String name, String version) {
super(String.format(NOT_FOUND_BY_FIND_DATA, name, version));
log.error(getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package gov.cms.madie.madiefhirservice.exceptions;

import gov.cms.madie.models.library.CqlLibrary;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(value = HttpStatus.NOT_FOUND)
@Slf4j
public class MissingCqlException extends RuntimeException {
private static final String NONE_FOUND = "Cannot find CQL for library name: %s, version: %s";

public MissingCqlException(CqlLibrary library) {
super(String.format(NONE_FOUND, library.getCqlLibraryName(), library.getVersion()));
log.warn(getMessage());
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package gov.cms.madie.madiefhirservice.services;

import gov.cms.madie.madiefhirservice.exceptions.CqlLibraryNotFoundException;
import gov.cms.madie.models.library.CqlLibrary;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;

@Service
@Slf4j
@RequiredArgsConstructor
public class CqlLibraryService {

private final RestTemplate restTemplate;

@Value("${madie.library.service.baseUrl}")
private String madieLibraryService;

@Value("${madie.library.service.versioned.uri}")
private String librariesVersionedUri;

public CqlLibrary getLibrary(String name, String version, String accessToken) {
URI uri = buildMadieLibraryServiceUri(name, version);
log.debug("Getting Madie library: {} ", uri);

HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", accessToken);

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

if (responseEntity.getStatusCode().is2xxSuccessful()) {
if (responseEntity.hasBody()) {
log.debug("Retrieved a valid cqlPayload");
return responseEntity.getBody();
} else {
log.error("Cannot find Cql payload in the response");
return null;
}
} else if (responseEntity.getStatusCode().equals(HttpStatus.NOT_FOUND)) {
throw new CqlLibraryNotFoundException(name, version);
} else if (responseEntity.getStatusCode().equals(HttpStatus.CONFLICT)) {
log.error(
"Multiple libraries found with name: {}, version: {}, but only one was expected",
name,
version);
}
return null;
}

private URI buildMadieLibraryServiceUri(String name, String version) {
return UriComponentsBuilder.fromHttpUrl(madieLibraryService + librariesVersionedUri)
.queryParam("name", name)
.queryParam("version", version)
.build()
.encode()
.toUri();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,89 +2,38 @@

import gov.cms.madie.madiefhirservice.cql.LibraryCqlVisitor;
import gov.cms.madie.madiefhirservice.cql.LibraryCqlVisitorFactory;
import gov.cms.madie.madiefhirservice.exceptions.DuplicateLibraryException;
import gov.cms.madie.madiefhirservice.exceptions.HapiLibraryNotFoundException;
import gov.cms.madie.madiefhirservice.exceptions.LibraryAttachmentNotFoundException;
import gov.cms.madie.madiefhirservice.hapi.HapiFhirServer;
import gov.cms.madie.madiefhirservice.exceptions.MissingCqlException;
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;
import org.apache.commons.lang3.tuple.Pair;
import org.hl7.fhir.r4.model.Attachment;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Library;
import org.hl7.fhir.r4.model.Narrative;
import org.hl7.fhir.r4.model.Narrative.NarrativeStatus;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

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

@Service
@Slf4j
@RequiredArgsConstructor
public class LibraryService {
private final HapiFhirServer hapiFhirServer;

private final CqlLibraryService cqlLibraryService;
private final LibraryTranslatorService libraryTranslatorService;
private final LibraryCqlVisitorFactory libCqlVisitorFactory;
private final HumanReadableService humanReadableService;

public String getLibraryCql(String name, String version) {

Bundle bundle = hapiFhirServer.fetchLibraryBundleByNameAndVersion(name, version);
if (bundle.hasEntry()) {
return processBundle(name, version, bundle);
} else {
throw new HapiLibraryNotFoundException(name, version);
}
}

public CqlLibrary getLibraryResourceAsCqlLibrary(String name, String version) {
Bundle bundle = hapiFhirServer.fetchLibraryBundleByNameAndVersion(name, version);

if (bundle.hasEntry()) {
Optional<Library> optional =
hapiFhirServer.findLibraryResourceInBundle(bundle, Library.class);
return optional
.map(libraryTranslatorService::convertToCqlLibrary)
.orElseThrow(() -> new HapiLibraryNotFoundException(name, version));
} else {
throw new HapiLibraryNotFoundException(name, version);
}
}

public boolean isLibraryResourcePresent(String name, String version) {

Bundle bundle = hapiFhirServer.fetchLibraryBundleByNameAndVersion(name, version);
if (!bundle.hasEntry()) {
return false;
}

return hapiFhirServer.findLibraryResourceInBundle(bundle, Library.class).isPresent();
}

private String processBundle(String name, String version, Bundle bundle) {

Optional<Library> optional = hapiFhirServer.findLibraryResourceInBundle(bundle, Library.class);
if (optional.isPresent()) {
return getCqlFromHapiLibrary(optional.get());
} else {
throw new HapiLibraryNotFoundException(name, version);
}
}

private String getCqlFromHapiLibrary(Library library) {

List<Attachment> attachments = library.getContent();

if (CollectionUtils.isEmpty(attachments)) {
throw new LibraryAttachmentNotFoundException(library);
public String getLibraryCql(String name, String version, final String accessToken) {
CqlLibrary library = cqlLibraryService.getLibrary(name, version, accessToken);
if (StringUtils.isBlank(library.getCql())) {
throw new MissingCqlException(library);
}
Attachment cql = findCqlAttachment(library);
return new String(cql.getData());
return cqlLibraryService.getLibrary(name, version, accessToken).getCql();
}

private Attachment findCqlAttachment(Library library) {
Expand All @@ -94,45 +43,36 @@ private Attachment findCqlAttachment(Library library) {
.orElseThrow(() -> new LibraryAttachmentNotFoundException(library, "text/cql"));
}

public Library createLibraryResourceForCqlLibrary(CqlLibrary cqlLibrary) {
boolean isLibraryPresent =
isLibraryResourcePresent(
cqlLibrary.getCqlLibraryName(), cqlLibrary.getVersion().toString());

if (isLibraryPresent) {
throw new DuplicateLibraryException(
cqlLibrary.getCqlLibraryName(), cqlLibrary.getVersion().toString());
}

public Library cqlLibraryToFhirLibrary(CqlLibrary cqlLibrary, final String bundleType) {
Library library = libraryTranslatorService.convertToFhirLibrary(cqlLibrary, null);
library.setText(createLibraryNarrativeText(library));
hapiFhirServer.createResource(library);
if (BundleUtil.MEASURE_BUNDLE_TYPE_EXPORT.equals(bundleType)) {
library.setText(createLibraryNarrativeText(library));
}
return library;
}

public void getIncludedLibraries(String cql, Map<String, Library> libraryMap) {
public void getIncludedLibraries(
String cql,
Map<String, Library> libraryMap,
final String bundleType,
final String accessToken) {
if (StringUtils.isBlank(cql) || libraryMap == null) {
log.error("Invalid method arguments provided to getIncludedLibraries");
throw new IllegalArgumentException("Please provide valid arguments.");
}

LibraryCqlVisitor visitor = libCqlVisitorFactory.visit(cql);
for (Pair<String, String> libraryNameValuePair : visitor.getIncludedLibraries()) {
Optional<Library> optionalLibrary =
hapiFhirServer.fetchHapiLibrary(
libraryNameValuePair.getLeft(), libraryNameValuePair.getRight());
if (optionalLibrary.isPresent()) {
Library library = optionalLibrary.get();
String key = library.getName() + library.getVersion();
if (!libraryMap.containsKey(key)) {
libraryMap.put(key, library);
}
Attachment attachment = findCqlAttachment(library);
getIncludedLibraries(new String(attachment.getData()), libraryMap);
} else {
throw new HapiLibraryNotFoundException(
libraryNameValuePair.getLeft(), libraryNameValuePair.getRight());
CqlLibrary cqlLibrary =
cqlLibraryService.getLibrary(
libraryNameValuePair.getLeft(), libraryNameValuePair.getRight(), accessToken);
Library library = cqlLibraryToFhirLibrary(cqlLibrary, bundleType);
String key = library.getName() + library.getVersion();
if (!libraryMap.containsKey(key)) {
libraryMap.put(key, library);
}
Attachment attachment = findCqlAttachment(library);
getIncludedLibraries(new String(attachment.getData()), libraryMap, bundleType, accessToken);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ public class MeasureBundleService {
*/
public Bundle createMeasureBundle(
Measure madieMeasure, Principal principal, String bundleType, String accessToken) {
log.info("Generating measure bundle for measure {}", madieMeasure.getId());
log.info(
"Generating measure bundle of type [{}] for measure {}", bundleType, madieMeasure.getId());
madieMeasure.setCql(CqlFormatter.formatCql(madieMeasure.getCql(), principal));

org.hl7.fhir.r4.model.Measure measure =
Expand All @@ -57,7 +58,7 @@ public Bundle createMeasureBundle(
new Bundle().setType(Bundle.BundleType.TRANSACTION).addEntry(measureEntryComponent);
// Bundle entries for all the library resources of a MADiE Measure
List<Bundle.BundleEntryComponent> libraryEntryComponents =
createBundleComponentsForLibrariesOfMadieMeasure(madieMeasure);
createBundleComponentsForLibrariesOfMadieMeasure(madieMeasure, bundleType, accessToken);
libraryEntryComponents.forEach(bundle::addEntry);

if (BundleUtil.MEASURE_BUNDLE_TYPE_EXPORT.equals(bundleType)) {
Expand Down Expand Up @@ -90,11 +91,12 @@ public Bundle createMeasureBundle(
* @return list of Library BundleEntryComponents
*/
public List<Bundle.BundleEntryComponent> createBundleComponentsForLibrariesOfMadieMeasure(
Measure madieMeasure) {
Measure madieMeasure, final String bundleType, final String accessToken) {
Library library = getMeasureLibraryResourceForMadieMeasure(madieMeasure);
Bundle.BundleEntryComponent mainLibraryBundleComponent = getBundleEntryComponent(library);
Map<String, Library> includedLibraryMap = new HashMap<>();
libraryService.getIncludedLibraries(madieMeasure.getCql(), includedLibraryMap);
libraryService.getIncludedLibraries(
madieMeasure.getCql(), includedLibraryMap, bundleType, accessToken);
List<Bundle.BundleEntryComponent> libraryBundleComponents =
includedLibraryMap.values().stream()
.map(this::getBundleEntryComponent)
Expand Down
5 changes: 5 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ madie:
service:
base-url: ${ELM_TRANSLATOR_SERVICE_URL:http://localhost:8084/api}
effective-data-requirements-uri: /effective-data-requirements
library:
service:
baseUrl: ${CQL_LIBRARY_SERVICE_URL:http://localhost:8082/api}
versioned:
uri: /cql-libraries/versioned
#springdoc:
# swagger-ui:
# path: ${SWAGGER_PATH:/swagger}
Expand Down
Loading

0 comments on commit 5d20953

Please sign in to comment.