From 7dbd46c5629c89867c509488598f9e801e2c1cee Mon Sep 17 00:00:00 2001 From: Chris Moesel Date: Fri, 1 Dec 2023 14:48:28 -0500 Subject: [PATCH] Version 2.3.0 w/ support for Translator 3.3.2 (#39) * 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 --- Dockerfile | 2 +- README.md | 3 +- pom.xml | 13 ++- .../TranslationResource.java | 90 ++++++++----------- .../TranslationResourceTest.java | 17 ++++ .../bonnie/cqlTranslationServer/NeedsUCUM.cql | 21 +++++ 6 files changed, 91 insertions(+), 55 deletions(-) create mode 100644 src/test/resources/org/mitre/bonnie/cqlTranslationServer/NeedsUCUM.cql diff --git a/Dockerfile b/Dockerfile index fd130ca..f98eca5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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"] diff --git a/README.md b/README.md index a859ff5..dd571cb 100644 --- a/README.md +++ b/README.md @@ -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._ @@ -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 | diff --git a/pom.xml b/pom.xml index 4e34d8f..968a8da 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.mitre.bonnie cqlTranslationServer jar - 2.2.0 + 2.3.0 cqlTranslationServer @@ -112,6 +112,15 @@ commons-cli 1.6.0 + + + org.ogce + xpp3 + 1.1.6 + @@ -175,7 +184,7 @@ - 2.11.0 + 3.3.2 2.41 UTF-8 diff --git a/src/main/java/org/mitre/bonnie/cqlTranslationServer/TranslationResource.java b/src/main/java/org/mitre/bonnie/cqlTranslationServer/TranslationResource.java index 6bb2afc..90a2e73 100644 --- a/src/main/java/org/mitre/bonnie/cqlTranslationServer/TranslationResource.java +++ b/src/main/java/org/mitre/bonnie/cqlTranslationServer/TranslationResource.java @@ -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; @@ -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 @@ -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 ); @@ -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 params) { - try { - UcumService ucumService = null; - LibraryBuilder.SignatureLevel signatureLevel = LibraryBuilder.SignatureLevel.None; - List 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 params) { + LibraryBuilder.SignatureLevel signatureLevel = LibraryBuilder.SignatureLevel.None; + List 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) { diff --git a/src/test/java/org/mitre/bonnie/cqlTranslationServer/TranslationResourceTest.java b/src/test/java/org/mitre/bonnie/cqlTranslationServer/TranslationResourceTest.java index 07751d7..8b298a5 100644 --- a/src/test/java/org/mitre/bonnie/cqlTranslationServer/TranslationResourceTest.java +++ b/src/test/java/org/mitre/bonnie/cqlTranslationServer/TranslationResourceTest.java @@ -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'])"); diff --git a/src/test/resources/org/mitre/bonnie/cqlTranslationServer/NeedsUCUM.cql b/src/test/resources/org/mitre/bonnie/cqlTranslationServer/NeedsUCUM.cql new file mode 100644 index 0000000..954fa96 --- /dev/null +++ b/src/test/resources/org/mitre/bonnie/cqlTranslationServer/NeedsUCUM.cql @@ -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 '%') \ No newline at end of file