Skip to content

Commit

Permalink
Version 2.3.0 w/ support for Translator 3.3.2 (#39)
Browse files Browse the repository at this point in the history
* Version 2.3.0 w/ support for Translator 3.3.2

Update the translator to the latest one in the 3.x line. This required code changes, as the Translator classes have been refactored.

* Add xpp3 library to support UCUM operations
  • Loading branch information
cmoesel authored Dec 1, 2023
1 parent b5ff2fd commit 7dbd46c
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 55 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ EXPOSE 8080

# execute it
# CMD ["mvn", "exec:java"]
CMD ["java", "-jar", "target/cqlTranslationServer-2.2.0.jar", "-d"]
CMD ["java", "-jar", "target/cqlTranslationServer-2.3.0.jar", "-d"]
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Build:

Execute via the command line:

java -jar target/cqlTranslationServer-2.2.0.jar
java -jar target/cqlTranslationServer-2.3.0.jar

_NOTE: The cqlTranslationServer jar assumes that all dependency jars are located in a `libs` directory relative to the jar's location. If you move the jar from the `target` directory, you will need to move the `target/libs` directory as well. This project no longer produces an "uber-jar", as the CQL-to-ELM classes do not function properly when repackaged into a single jar file._

Expand All @@ -18,6 +18,7 @@ CQL Translation Service versions prior to version 2.0.0 always mirrored the CQL

| CQL Translation Service | CQL Tools |
|-------------------------|-----------------------------------------|
| 2.3.0 | 3.3.2 |
| 2.2.0 | 2.11.0 |
| 2.1.0 | 2.10.0 |
| 2.0.0 | 2.7.0 |
Expand Down
13 changes: 11 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<groupId>org.mitre.bonnie</groupId>
<artifactId>cqlTranslationServer</artifactId>
<packaging>jar</packaging>
<version>2.2.0</version>
<version>2.3.0</version>
<name>cqlTranslationServer</name>

<repositories>
Expand Down Expand Up @@ -112,6 +112,15 @@
<artifactId>commons-cli</artifactId>
<version>1.6.0</version>
</dependency>
<!--
We now need to supply our own xpp3 implementation. Use the same one as is used here:
https://github.com/cqframework/clinical_quality_language/blob/master/Src/java/buildSrc/src/main/groovy/cql.fhir-conventions.gradle
-->
<dependency>
<groupId>org.ogce</groupId>
<artifactId>xpp3</artifactId>
<version>1.1.6</version>
</dependency>
</dependencies>

<build>
Expand Down Expand Up @@ -175,7 +184,7 @@
</build>

<properties>
<cql.version>2.11.0</cql.version>
<cql.version>3.3.2</cql.version>
<jersey.version>2.41</jersey.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,15 @@
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;
import org.cqframework.cql.cql2elm.CqlCompilerException;

import org.cqframework.cql.cql2elm.CqlCompilerOptions;
import org.cqframework.cql.cql2elm.CqlTranslator;
import org.cqframework.cql.cql2elm.CqlTranslatorOptions.Options;
import org.cqframework.cql.cql2elm.LibraryBuilder;
import org.cqframework.cql.cql2elm.CqlCompilerOptions.Options;
import org.cqframework.cql.cql2elm.LibraryManager;
import org.cqframework.cql.cql2elm.ModelManager;
import org.cqframework.cql.cql2elm.CqlCompilerException.ErrorSeverity;
import org.cqframework.cql.cql2elm.quick.FhirLibrarySourceProvider;
import org.fhir.ucum.UcumEssenceService;
import org.fhir.ucum.UcumException;
import org.fhir.ucum.UcumService;
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;

Expand Down Expand Up @@ -69,34 +68,36 @@ public class TranslationResource {
Options.EnableResultTypes)
);
}};
private final ModelManager modelManager;
private final LibraryManager libraryManager;

public TranslationResource() {
this.modelManager = new ModelManager();
this.libraryManager = new LibraryManager(modelManager);
// FHIR library source provider is always needed for FHIR and harmless for other models
libraryManager.getLibrarySourceLoader().registerProvider(new FhirLibrarySourceProvider());
}

@POST
@Consumes(CQL_TEXT_TYPE)
@Produces(ELM_XML_TYPE)
public Response cqlToElmXml(File cql, @Context UriInfo info) {
CqlTranslator translator = getTranslator(cql, info.getQueryParameters());
ResponseBuilder resp = getResponse(translator);
resp = resp.entity(translator.toXml()).type(ELM_XML_TYPE);
return resp.build();
try {
LibraryManager libraryManager = this.getLibraryManager(info.getQueryParameters());
CqlTranslator translator = CqlTranslator.fromFile(cql, libraryManager);
ResponseBuilder resp = getResponse(translator);
resp = resp.entity(translator.toXml()).type(ELM_XML_TYPE);
return resp.build();
} catch (IOException e) {
throw new TranslationFailureException("Unable to read request");
}

}

@POST
@Consumes(CQL_TEXT_TYPE)
@Produces(ELM_JSON_TYPE)
public Response cqlToElmJson(File cql, @Context UriInfo info) {
CqlTranslator translator = getTranslator(cql, info.getQueryParameters());
ResponseBuilder resp = getResponse(translator);
resp = resp.entity(translator.toJson()).type(ELM_JSON_TYPE);
return resp.build();
try {
LibraryManager libraryManager = this.getLibraryManager(info.getQueryParameters());
CqlTranslator translator = CqlTranslator.fromFile(cql, libraryManager);
ResponseBuilder resp = getResponse(translator);
resp = resp.entity(translator.toJson()).type(ELM_JSON_TYPE);
return resp.build();
} catch (IOException e) {
throw new TranslationFailureException("Unable to read request");
}
}

@POST
Expand All @@ -110,12 +111,12 @@ public Response cqlPackageToElmPackage(
// Jersey doesn't support parsing multiple value headers by comma, so we need to do it
// for ourselves. See https://github.com/jersey/jersey/issues/2436
try {
MultipartLibrarySourceProvider lsp = new MultipartLibrarySourceProvider(pkg);
libraryManager.getLibrarySourceLoader().registerProvider(lsp);
LibraryManager libraryManager = this.getLibraryManager(info.getQueryParameters());
libraryManager.getLibrarySourceLoader().registerProvider(new MultipartLibrarySourceProvider(pkg));
FormDataMultiPart translatedPkg = new FormDataMultiPart();
for (String fieldId: pkg.getFields().keySet()) {
for (FormDataBodyPart part: pkg.getFields(fieldId)) {
CqlTranslator translator = getTranslator(part.getEntityAs(File.class), info.getQueryParameters());
CqlTranslator translator = CqlTranslator.fromFile(part.getEntityAs(File.class), libraryManager);
for( String format : targetFormats ) {
for( String subformat : format.split(",") ) {
MediaType targetFormat = MediaType.valueOf( subformat );
Expand All @@ -134,37 +135,24 @@ public Response cqlPackageToElmPackage(
return resp.build();
} catch (IOException ex) {
throw new TranslationFailureException("Unable to read request");
} finally {
// Clear the package-based source provider then add back the FHIR library source provider
libraryManager.getLibrarySourceLoader().clearProviders();
libraryManager.getLibrarySourceLoader().registerProvider(new FhirLibrarySourceProvider());
}
}

private CqlTranslator getTranslator(File cql, MultivaluedMap<String, String> params) {
try {
UcumService ucumService = null;
LibraryBuilder.SignatureLevel signatureLevel = LibraryBuilder.SignatureLevel.None;
List<Options> optionsList = new ArrayList<>();
for (String key: params.keySet()) {
if (PARAMS_TO_OPTIONS_MAP.containsKey(key) && Boolean.parseBoolean(params.getFirst(key))) {
optionsList.addAll(PARAMS_TO_OPTIONS_MAP.get(key));
} else if (key.equals("validate-units") && Boolean.parseBoolean(params.getFirst(key))) {
try {
ucumService = new UcumEssenceService(UcumEssenceService.class.getResourceAsStream("/ucum-essence.xml"));
} catch (UcumException e) {
throw new TranslationFailureException("Cannot load UCUM service to validate units");
}
} else if (key.equals("signatures")) {
signatureLevel = LibraryBuilder.SignatureLevel.valueOf(params.getFirst("signatures"));
}
private LibraryManager getLibraryManager(MultivaluedMap<String, String> params) {
LibraryBuilder.SignatureLevel signatureLevel = LibraryBuilder.SignatureLevel.None;
List<Options> optionsList = new ArrayList<>();
for (String key: params.keySet()) {
if (PARAMS_TO_OPTIONS_MAP.containsKey(key) && Boolean.parseBoolean(params.getFirst(key))) {
optionsList.addAll(PARAMS_TO_OPTIONS_MAP.get(key));
} else if (key.equals("signatures")) {
signatureLevel = LibraryBuilder.SignatureLevel.valueOf(params.getFirst("signatures"));
}
Options[] options = optionsList.toArray(new Options[optionsList.size()]);
return CqlTranslator.fromFile(cql, modelManager, libraryManager, ucumService, CqlCompilerException.ErrorSeverity.Info,
signatureLevel, options);
} catch (IOException e) {
throw new TranslationFailureException("Unable to read request");
}
Options[] options = optionsList.toArray(new Options[optionsList.size()]);
LibraryManager libraryManager = new LibraryManager(new ModelManager(), new CqlCompilerOptions(ErrorSeverity.Info, signatureLevel, options));
// FHIR library source provider is always needed for FHIR and harmless for other models
libraryManager.getLibrarySourceLoader().registerProvider(new FhirLibrarySourceProvider());
return libraryManager;
}

private ResponseBuilder getResponse(CqlTranslator translator) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,23 @@ public void testMultipartRequestAsJsonAndXml() throws Exception {
}
}

@Test
public void testLibraryThatNeedsUCUM() {
File file = new File(TranslationResourceTest.class.getResource("NeedsUCUM.cql").getFile());
Response resp = target.path("translator").queryParam("signatures", "All").request(TranslationResource.ELM_JSON_TYPE).post(Entity.entity(file, TranslationResource.CQL_TEXT_TYPE));
assertEquals(Status.OK.getStatusCode(), resp.getStatus());
assertEquals(TranslationResource.ELM_JSON_TYPE, resp.getMediaType().toString());
assertTrue(resp.hasEntity());
JsonReader reader = Json.createReader(new StringReader(resp.readEntity(String.class)));
JsonObject obj = reader.readObject();
JsonObject library = obj.getJsonObject("library");
JsonArray annotations = library.getJsonArray("annotation");
assertEquals(1, annotations.size());
JsonObject identifier = library.getJsonObject("identifier");
assertEquals("NeedsUCUM", identifier.getString("id"));
assertEquals("0.0.1", identifier.getString("version"));
}

private Document parseAndValidateXml( BodyPart input, String expectedId, String expectedVersion, int expectedErrors ) throws Exception {
Document doc = parseXml(input.getEntityAs(String.class));
String errorCount = applyXPath(doc, "count(/elm:library/elm:annotation[@errorType='syntax'])");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
library NeedsUCUM version '0.0.1'

using FHIR version '4.0.1'

include FHIRHelpers version '4.0.1'

valueset "test-vs": 'http://example.com/test-vs'

context Patient

define "Query ExpressionRef with Value Comparison":
("Simple Observation Query".value as Quantity) > 9 '%'

define "Simple Observation Query":
Last([Observation: "test-vs"] O
where O.status in {'final', 'amended', 'corrected'})

define "Has Elevated Value With Where":
Last([Observation: "test-vs"] O
where O.status in {'final', 'amended', 'corrected'}
and (O.value as Quantity) > 9 '%')

0 comments on commit 7dbd46c

Please sign in to comment.