v3 to v4 Migration - cqframework/clinical_quality_language GitHub Wiki

This page outlines the changes to this repository that are included with the v4 release.

The #1462 pull request converted the cql-to-elm Java project and its dependencies to Kotlin Multiplatform (KMP) targeting the JVM and JavaScript. The Java translator maintains feature parity with the previous version (see the summary of changes below), while the new JS target makes it possible to convert CQL to ELM in the browser and Node.js.

Summary of changes for Java users

The Java API of the translator remains largely the same, however some changes have been made to accommodate KMP conventions.

Dependency management

  • Starting with v4, the releases will be published under the new org.cqframework group ID on Maven Central.
  • Previously, you needed to add elm-(jackson|jaxb) and model-(jackson|jaxb) as extra dependencies to your project. This is no longer required because the default multiplatform serialization functionality is now included with cql-to-elm. The elm-(jackson|jaxb) and model-(jackson|jaxb) projects have been removed.
  • For ucum-java-based unit validation to work in the translator on the JVM, you now need to add ucum as a dependency (a new project in this repository). Alternatively, you can provide your own implementation of the UcumService interface.

Class and interface relocation, removals, and other API changes

Notable changes, grouped by the affected class or interface:

  • CqlTranslator:
    • The fromStream() static method (all overloads) is replaced with fromSource() which accepts Source instead of Java InputStream.
    • The fromFile() static method overloads now accept KMP Path instead of Java File.
    • The toXml(), toJson(), convertToXml(), convertToJson() methods have new overloads that additionally accept an implementation of ElmLibraryWriterProvider, allowing you to provide a custom ELM serializer.
  • LibraryManager:
    • A new constructor overload additionally accepts an implementation of ElmLibraryReaderProvider, allowing you to provide a custom ELM deserializer.
  • LibrarySourceProvider:
    • The getLibrarySource() and getLibraryContent() methods now return Source instead of Java InputStream.
  • DefaultLibrarySourceProvider:
    • The constructor accepts KMP Path instead of Java Path.
  • ElmLibraryWriter:
    • The write() method accepts Sink instead of Java Writer.
  • ElmLibraryReader:
    • The read() overloads accept String or Source.
  • The org.cqframework.cql.elm.tracking.(Trackable|TrackBack) classes are moved to org.cqframework.cql.cql2elm.tracking.(Trackable|TrackBack).
  • ModelInfoReader, ModelInfoReaderFactory, ModelInfoReaderProvider are all replaced with a single multiplatform parseModelInfoXml() method.
  • The CqlTranslatorOptionsMapper.fromFile() static method is replaced with CqlTranslatorOptions.fromFile() accepting KMP Path.

cql-to-elm's new KMP project structure

One of the motivations for converting to KMP was to publish the JavaScript version of the CQL compiler. The new JS variant is now built alongside the JVM version as part of the same cql-to-elm KMP project.

The JVM and JS variants use the same core CQL to ELM translator logic under commonMain.

One of the main functionalities unique to the JVM is the automatic loading of ModelInfoProvider, LibrarySourceProvider, and UcumService implementations which relies on ServiceLoader and (for ModelInfoProvider and LibrarySourceProvider) works the same way as before. The JS variant requires you to register the providers explicitly when configuring the ModelManager and LibraryManager.

Kotlin code generation and multiplatform serialization

The previous version of the project used JAXB's xjc tool to generate Java classes from the ELM and ModelInfo XML schemas. The generated code was specific to the JVM and included JAXB annotations and methods for serialization and deserialization. At run time, the translator used either JAXB or Jackson to read and write ELM and ModelInfo XML and JSON, depending on which of elm-(jackson|jaxb) and model-(jackson|jaxb) were included. The translator relied on ServiceLoader to load the implementations of the ElmLibraryReaderProvider, ElmLibraryWriterProvider, and ModelInfoReaderProvider interfaces to use for this purpose.

In v4, a custom plugin is used to generate pure-Kotlin ELM and model info classes from the same XML schemas. These classes do not depend on JAXB but otherwise have the same structure as the previous Java classes. Runtime parsing and writing of XML is now handled by the custom multiplatform XML methods, while kotlinx.serialization is used for JSON. This functionality is available in the translator by default, so no peer dependencies are required.

In case you need to customize the serialization behavior for ELM, you can use the new overloads of the CqlTranslator methods and LibraryManager constructor to provide your own ElmLibraryReaderProvider and ElmLibraryWriterProvider implementations.

The new CQL compiler for JavaScript environments

The JS variant of the compiler is currently in beta, and its API may change in a backwards-incompatible way before reaching a stable release. The end goal is, however, to have the common API across the JVM and JS variants with minimal differences.

Here is a simple example of using the JS compiler to translate a CQL library to ELM:

import { ModelManager, LibraryManager, CqlTranslator } from "@cqframework/cql/cql-to-elm";

const modelManager = new ModelManager();
// Register the necessary model info providers with the model manager here
const libraryManager = new LibraryManager(modelManager);
// Register the necessary library source providers with the library manager here
const cqlTranslator = CqlTranslator.fromText("library Test version '1.0.0'", libraryManager);
const elmJson = cqlTranslator.toJson();

A more complete example, including how to set up a web app that uses the JS compiler, can be found in Src/js/cql-to-elm-ui.