Skip to content

Commit 1198d2e

Browse files
authored
Merge pull request #465 from MeasureAuthoringTool/feature/MAT-10319-execution-bundle-with-npm-libraries
MAT-10319: Executable Bundle using NPM Libraries
2 parents 3a67177 + 178ce17 commit 1198d2e

7 files changed

Lines changed: 209 additions & 54 deletions

File tree

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@
198198
<dependency>
199199
<groupId>gov.cms.madie</groupId>
200200
<artifactId>madie-java-models</artifactId>
201-
<version>0.10.13-SNAPSHOT</version>
201+
<version>0.10.15-SNAPSHOT</version>
202202
</dependency>
203203
<dependency>
204204
<groupId>com.jayway.jsonpath</groupId>

src/main/java/gov/cms/madie/madiefhirservice/services/CqlLibraryService.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package gov.cms.madie.madiefhirservice.services;
22

33
import gov.cms.madie.madiefhirservice.exceptions.CqlLibraryNotFoundException;
4-
import gov.cms.madie.models.library.CqlLibrary;
4+
import gov.cms.madie.models.dto.CqlLibraryDto;
55
import lombok.RequiredArgsConstructor;
66
import lombok.extern.slf4j.Slf4j;
77
import org.cqframework.cql.cql2elm.CqlCompilerException;
@@ -17,6 +17,7 @@
1717
import org.springframework.web.util.UriComponentsBuilder;
1818

1919
import java.net.URI;
20+
import java.util.Optional;
2021

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

34-
@Cacheable(value = "libraries", key = "{ #root.methodName, #name, #version }")
35-
public CqlLibrary getLibrary(
35+
@Cacheable(value = "libraries", key = "{ #root.methodName, #name, #version, #namespacePrefix }")
36+
public CqlLibraryDto getLibrary(
3637
String name,
3738
String version,
39+
Optional<String> namespacePrefix,
3840
String accessToken,
3941
CqlCompilerException.ErrorSeverity errorSeverity) {
40-
URI uri = buildMadieLibraryServiceUri(name, version, errorSeverity);
42+
URI uri = buildMadieLibraryServiceUri(name, version, namespacePrefix, errorSeverity);
4143
HttpHeaders headers = new HttpHeaders();
4244
headers.add("Authorization", accessToken);
4345

44-
ResponseEntity<CqlLibrary> responseEntity =
45-
restTemplate.exchange(uri, HttpMethod.GET, new HttpEntity<>(headers), CqlLibrary.class);
46+
ResponseEntity<CqlLibraryDto> responseEntity =
47+
restTemplate.exchange(uri, HttpMethod.GET, new HttpEntity<>(headers), CqlLibraryDto.class);
4648

4749
if (responseEntity.getStatusCode().is2xxSuccessful()) {
4850
if (responseEntity.hasBody()) {
@@ -78,11 +80,15 @@ public CqlLibrary getLibrary(
7880
* @return
7981
*/
8082
private URI buildMadieLibraryServiceUri(
81-
String name, String version, CqlCompilerException.ErrorSeverity errorSeverity) {
83+
String name,
84+
String version,
85+
Optional<String> namespacePrefix,
86+
CqlCompilerException.ErrorSeverity errorSeverity) {
8287
return UriComponentsBuilder.fromUriString(madieLibraryService + librariesVersionedUri)
8388
.queryParam("name", name)
8489
.queryParam("version", version)
8590
.queryParam("errorSeverity", errorSeverity)
91+
.queryParamIfPresent("namespacePrefix", namespacePrefix)
8692
.build()
8793
.encode()
8894
.toUri();

src/main/java/gov/cms/madie/madiefhirservice/services/LibraryService.java

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
import gov.cms.madie.madiefhirservice.cql.LibraryCqlVisitor;
44
import gov.cms.madie.madiefhirservice.cql.LibraryCqlVisitorFactory;
5+
import gov.cms.madie.models.dto.CqlLibraryDto;
56
import gov.cms.madie.madiefhirservice.exceptions.LibraryAttachmentNotFoundException;
67
import gov.cms.madie.madiefhirservice.utils.BundleUtil;
7-
import gov.cms.madie.models.library.CqlLibrary;
88
import lombok.RequiredArgsConstructor;
99
import lombok.extern.slf4j.Slf4j;
1010
import org.apache.commons.lang3.StringUtils;
@@ -17,6 +17,7 @@
1717
import org.springframework.stereotype.Service;
1818

1919
import java.util.Map;
20+
import java.util.Optional;
2021

2122
@Service
2223
@Slf4j
@@ -29,7 +30,7 @@ public class LibraryService {
2930
private final HumanReadableService humanReadableService;
3031

3132
public Library cqlLibraryToFhirLibrary(
32-
CqlLibrary cqlLibrary, final String bundleType, String accessToken) {
33+
CqlLibraryDto cqlLibrary, final String bundleType, String accessToken) {
3334
Library library = libraryTranslatorService.convertToFhirLibrary(cqlLibrary, null, accessToken);
3435
if (BundleUtil.MEASURE_BUNDLE_TYPE_EXPORT.equals(bundleType)) {
3536
library.setText(createLibraryNarrativeText(library));
@@ -52,10 +53,12 @@ public void getIncludedLibraries(
5253
for (Pair<String, String> libraryNameValuePair : visitor.getIncludedLibraries()) {
5354
String key = libraryNameValuePair.getLeft() + libraryNameValuePair.getRight();
5455
if (!libraryMap.containsKey(key)) {
55-
CqlLibrary cqlLibrary =
56+
var libraryParts = parseLibraryString(libraryNameValuePair.getLeft());
57+
CqlLibraryDto cqlLibrary =
5658
cqlLibraryService.getLibrary(
57-
libraryNameValuePair.getLeft(),
58-
libraryNameValuePair.getRight(),
59+
libraryParts[1], // name
60+
libraryNameValuePair.getRight(), // version
61+
Optional.ofNullable(libraryParts[0]), // prefix
5962
accessToken,
6063
errorSeverity);
6164
// Todo If the library is already in libraryMap, we can skip the call to
@@ -72,6 +75,38 @@ public void getIncludedLibraries(
7275
}
7376
}
7477

78+
/**
79+
* Parses a library identifier into namespace and library name parts.
80+
*
81+
* <p>The split is performed on the last {@code '.'} character:
82+
*
83+
* <ul>
84+
* <li>If {@code fullLibraryString} is {@code null} or blank, returns {@code {"", ""}}.
85+
* <li>If no {@code '.'} is present, returns {@code {"", trimmedInput}}.
86+
* <li>If {@code '.'} is present, returns {@code {trimmedNamespace, trimmedLibraryName}}.
87+
* </ul>
88+
*
89+
* @param fullLibraryString the raw library identifier, optionally namespace-qualified
90+
* @return a two-element array where index {@code 0} is the namespace (or empty string) and index
91+
* {@code 1} is the library name (or empty string)
92+
*/
93+
public String[] parseLibraryString(String fullLibraryString) {
94+
if (fullLibraryString == null || fullLibraryString.trim().isEmpty()) {
95+
return new String[] {"", ""};
96+
}
97+
98+
int lastDotIndex = fullLibraryString.lastIndexOf('.');
99+
100+
if (lastDotIndex == -1) {
101+
return new String[] {"", fullLibraryString.trim()};
102+
}
103+
104+
String namespace = fullLibraryString.substring(0, lastDotIndex).trim();
105+
String libraryName = fullLibraryString.substring(lastDotIndex + 1).trim();
106+
107+
return new String[] {namespace, libraryName};
108+
}
109+
75110
private Narrative createLibraryNarrativeText(Library library) {
76111
Narrative narrative = new Narrative();
77112
narrative.setStatus(NarrativeStatus.EXTENSIONS);

src/main/java/gov/cms/madie/madiefhirservice/services/LibraryTranslatorService.java

Lines changed: 64 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import gov.cms.madie.madiefhirservice.constants.UriConstants;
44
import gov.cms.madie.madiefhirservice.cql.LibraryCqlVisitorFactory;
55
import gov.cms.madie.madiefhirservice.dto.CqlLibraryDetails;
6+
import gov.cms.madie.models.dto.CqlLibraryDto;
67
import gov.cms.madie.madiefhirservice.utils.FhirResourceHelpers;
78
import gov.cms.madie.models.library.CqlLibrary;
89
import lombok.extern.slf4j.Slf4j;
@@ -42,40 +43,48 @@ public LibraryTranslatorService(
4243

4344
public Library convertToFhirLibrary(
4445
CqlLibrary cqlLibrary, Set<String> expressions, String accessToken) {
45-
var visitor = libCqlVisitorFactory.visit(cqlLibrary.getCql());
46+
return convertToFhirLibrary(LibrarySource.from(cqlLibrary), expressions, accessToken);
47+
}
48+
49+
public Library convertToFhirLibrary(
50+
CqlLibraryDto cqlLibrary, Set<String> expressions, String accessToken) {
51+
return convertToFhirLibrary(LibrarySource.from(cqlLibrary), expressions, accessToken);
52+
}
53+
54+
private Library convertToFhirLibrary(
55+
LibrarySource cqlLibrary, Set<String> expressions, String accessToken) {
56+
var visitor = libCqlVisitorFactory.visit(cqlLibrary.cql());
4657
Library library = new Library();
47-
library.setId(cqlLibrary.getCqlLibraryName());
58+
library.setId(cqlLibrary.name());
4859
library.setLanguage("en");
49-
library.setName(cqlLibrary.getCqlLibraryName());
50-
library.setVersion(cqlLibrary.getVersion().toString());
60+
library.setName(cqlLibrary.name());
61+
library.setVersion(cqlLibrary.version());
5162
library.setDate(new Date());
5263
library.setStatus(Enumerations.PublicationStatus.ACTIVE);
5364
library.setPublisher(
54-
cqlLibrary.getPublisher() != null && StringUtils.isNotBlank(cqlLibrary.getPublisher())
55-
? cqlLibrary.getPublisher()
65+
cqlLibrary.publisher() != null && StringUtils.isNotBlank(cqlLibrary.publisher())
66+
? cqlLibrary.publisher()
5667
: UNKNOWN_VALUE);
57-
library.setDescription(Objects.toString(cqlLibrary.getDescription(), UNKNOWN_VALUE));
58-
library.setExperimental(cqlLibrary.isExperimental());
59-
library.setContent(
60-
createContent(cqlLibrary.getCql(), cqlLibrary.getElmJson(), cqlLibrary.getElmXml()));
68+
library.setDescription(Objects.toString(cqlLibrary.description(), UNKNOWN_VALUE));
69+
library.setExperimental(cqlLibrary.experimental());
70+
library.setContent(createContent(cqlLibrary.cql(), cqlLibrary.elmJson(), cqlLibrary.elmXml()));
6171
library.setType(createType(UriConstants.CodeSystem.LIBRARY_SYSTEM_TYPE_URI, SYSTEM_CODE));
62-
library.setUrl(
63-
FhirResourceHelpers.buildResourceFullUrl("Library", cqlLibrary.getCqlLibraryName()));
72+
library.setUrl(FhirResourceHelpers.buildResourceFullUrl("Library", cqlLibrary.name()));
6473
library.getExtension().addAll(visitor.getDrcExtensions());
6574
library.setMeta(createLibraryMeta());
66-
library.setTitle(cqlLibrary.getCqlLibraryName());
67-
library.setPublisher(cqlLibrary.getPublisher());
75+
library.setTitle(cqlLibrary.name());
76+
library.setPublisher(cqlLibrary.publisher());
6877
Identifier identifier = new Identifier();
6978
identifier.setUse(IdentifierUse.OFFICIAL);
7079
identifier.setSystem("https://madie.cms.gov/login");
71-
identifier.setValue(cqlLibrary.getId());
80+
identifier.setValue(cqlLibrary.id());
7281
library.setIdentifier(List.of(identifier));
7382
// Use the DataRequirementsProcessor to construct data requirements and related artifacts.
7483
Library libraryModuleDefinition =
7584
retrieveLibraryModuleDefinition(
7685
CqlLibraryDetails.builder()
77-
.libraryName(cqlLibrary.getCqlLibraryName())
78-
.cql(cqlLibrary.getCql())
86+
.libraryName(cqlLibrary.name())
87+
.cql(cqlLibrary.cql())
7988
.expressions(expressions)
8089
.build(),
8190
accessToken);
@@ -84,6 +93,44 @@ public Library convertToFhirLibrary(
8493
return library;
8594
}
8695

96+
private record LibrarySource(
97+
String id,
98+
String name,
99+
String version,
100+
String cql,
101+
String elmJson,
102+
String elmXml,
103+
String publisher,
104+
String description,
105+
boolean experimental) {
106+
107+
private static LibrarySource from(CqlLibrary cqlLibrary) {
108+
return new LibrarySource(
109+
cqlLibrary.getId(),
110+
cqlLibrary.getCqlLibraryName(),
111+
cqlLibrary.getVersion().toString(),
112+
cqlLibrary.getCql(),
113+
cqlLibrary.getElmJson(),
114+
cqlLibrary.getElmXml(),
115+
cqlLibrary.getPublisher(),
116+
cqlLibrary.getDescription(),
117+
cqlLibrary.isExperimental());
118+
}
119+
120+
private static LibrarySource from(CqlLibraryDto cqlLibrary) {
121+
return new LibrarySource(
122+
cqlLibrary.getId(),
123+
cqlLibrary.getCqlLibraryName(),
124+
cqlLibrary.getVersion(),
125+
cqlLibrary.getCql(),
126+
cqlLibrary.getElmJson(),
127+
cqlLibrary.getElmXml(),
128+
cqlLibrary.getPublisher(),
129+
cqlLibrary.getDescription(),
130+
cqlLibrary.isExperimental());
131+
}
132+
}
133+
87134
private Library retrieveLibraryModuleDefinition(
88135
CqlLibraryDetails cqlLibraryDetails, String accessToken) {
89136
org.hl7.fhir.r5.model.Library r5moduleDefinition =

src/test/java/gov/cms/madie/madiefhirservice/services/CqlLibraryServiceTest.java

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
package gov.cms.madie.madiefhirservice.services;
22

33
import gov.cms.madie.madiefhirservice.exceptions.CqlLibraryNotFoundException;
4-
import gov.cms.madie.models.common.Version;
5-
import gov.cms.madie.models.library.CqlLibrary;
4+
import gov.cms.madie.models.dto.CqlLibraryDto;
65
import org.cqframework.cql.cql2elm.CqlCompilerException;
76
import org.junit.jupiter.api.BeforeEach;
87
import org.junit.jupiter.api.Test;
@@ -18,6 +17,7 @@
1817
import org.springframework.web.client.RestTemplate;
1918

2019
import java.net.URI;
20+
import java.util.Optional;
2121

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

4444
@Test
4545
void getLibraryReturnsLibrary() {
46-
CqlLibrary theLibrary =
47-
CqlLibrary.builder()
48-
.cqlLibraryName("FHIRHelpers")
49-
.version(Version.parse("4.0.001"))
50-
.build();
51-
ResponseEntity<CqlLibrary> response = ResponseEntity.ok(theLibrary);
46+
CqlLibraryDto theLibrary =
47+
CqlLibraryDto.builder().cqlLibraryName("FHIRHelpers").version("4.0.001").build();
48+
ResponseEntity<CqlLibraryDto> response = ResponseEntity.ok(theLibrary);
5249
when(restTemplate.exchange(
5350
any(URI.class), any(HttpMethod.class), any(HttpEntity.class), any(Class.class)))
5451
.thenReturn(response);
55-
CqlLibrary output =
52+
CqlLibraryDto output =
5653
cqlLibraryService.getLibrary(
57-
"FHIRHelpers", "4.0.001", "OKTA_TOKEN", CqlCompilerException.ErrorSeverity.Info);
54+
"FHIRHelpers",
55+
"4.0.001",
56+
Optional.empty(),
57+
"OKTA_TOKEN",
58+
CqlCompilerException.ErrorSeverity.Info);
5859
assertThat(output, is(notNullValue()));
5960
assertThat(output, is(equalTo(theLibrary)));
6061
}
@@ -72,6 +73,7 @@ void getLibraryReturnsExceptionForLibraryNotFound() {
7273
cqlLibraryService.getLibrary(
7374
"FHIRHelpers",
7475
"4.0.001",
76+
Optional.empty(),
7577
"OKTA_TOKEN",
7678
CqlCompilerException.ErrorSeverity.Info));
7779
assertThat(
@@ -85,9 +87,13 @@ void getLibraryReturnsNullForConflict() {
8587
when(restTemplate.exchange(
8688
any(URI.class), any(HttpMethod.class), any(HttpEntity.class), any(Class.class)))
8789
.thenReturn(response);
88-
CqlLibrary output =
90+
CqlLibraryDto output =
8991
cqlLibraryService.getLibrary(
90-
"FHIRHelpers", "4.0.001", "OKTA_TOKEN", CqlCompilerException.ErrorSeverity.Info);
92+
"FHIRHelpers",
93+
"4.0.001",
94+
Optional.empty(),
95+
"OKTA_TOKEN",
96+
CqlCompilerException.ErrorSeverity.Info);
9197
assertThat(output, is(nullValue()));
9298
}
9399

@@ -97,9 +103,13 @@ void getLibraryReturnsNullForOkNoBody() {
97103
when(restTemplate.exchange(
98104
any(URI.class), any(HttpMethod.class), any(HttpEntity.class), any(Class.class)))
99105
.thenReturn(response);
100-
CqlLibrary output =
106+
CqlLibraryDto output =
101107
cqlLibraryService.getLibrary(
102-
"FHIRHelpers", "4.0.001", "OKTA_TOKEN", CqlCompilerException.ErrorSeverity.Info);
108+
"FHIRHelpers",
109+
"4.0.001",
110+
Optional.empty(),
111+
"OKTA_TOKEN",
112+
CqlCompilerException.ErrorSeverity.Info);
103113
assertThat(output, is(nullValue()));
104114
}
105115
}

0 commit comments

Comments
 (0)