Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Break out UCUM Service into its own module #1471

Merged
merged 3 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Src/java/cql-to-elm-cli/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ dependencies {
implementation(project(":qdm"))
implementation(project(":model-jaxb"))
implementation(project(":elm-jaxb"))
implementation(project(":ucum"))
implementation("net.sf.jopt-simple:jopt-simple:4.7")
implementation("org.slf4j:slf4j-simple:1.7.36")
implementation("org.glassfish.jaxb:jaxb-runtime:4.0.5")
Expand Down
2 changes: 1 addition & 1 deletion Src/java/cql-to-elm/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ dependencies {
api(project(":cql"))
api(project(":model"))
api(project(":elm"))
api("org.fhir:ucum:1.0.8")

// TODO: This dependencies are required due the the fact that the CqlTranslatorOptionsMapper lives
// in the cql-to-elm project. Ideally, we"d factor out all serialization dependencies into common
Expand All @@ -17,6 +16,7 @@ dependencies {
testImplementation(project(":model-jackson"))
testImplementation(project(":quick"))
testImplementation(project(":qdm"))
testImplementation(project(":ucum"))
testImplementation("com.github.reinert:jjschema:1.16")
testImplementation("com.tngtech.archunit:archunit:1.2.1")
testImplementation("org.skyscreamer:jsonassert:1.5.1")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,11 @@ import kotlin.collections.ArrayList
import kotlin.collections.HashMap
import kotlin.collections.HashSet
import org.cqframework.cql.cql2elm.model.CompiledLibrary
import org.cqframework.cql.cql2elm.ucum.UcumService
import org.cqframework.cql.cql2elm.ucum.UcumServiceFactory
import org.cqframework.cql.elm.serializing.ElmLibraryReaderFactory
import org.fhir.ucum.UcumEssenceService
import org.fhir.ucum.UcumException
import org.fhir.ucum.UcumService
import org.hl7.cql.model.NamespaceManager
import org.hl7.elm.r1.*
import org.slf4j.Logger
import org.slf4j.LoggerFactory

/**
* Manages a set of CQL libraries. As new library references are encountered during compilation, the
Expand All @@ -39,26 +36,7 @@ constructor(
var compiledLibraries: MutableMap<VersionedIdentifier, CompiledLibrary> =
libraryCache ?: HashMap()
val librarySourceLoader: LibrarySourceLoader = PriorityLibrarySourceLoader()
var ucumService: UcumService? = null
get() {
if (field == null) {
field = defaultUcumService
}
return field
}

@get:Synchronized
private val defaultUcumService: UcumService?
get() {
try {
return UcumEssenceService(
UcumEssenceService::class.java.getResourceAsStream("/ucum-essence.xml")
)
} catch (e: UcumException) {
logger.warn("Error creating shared UcumService", e)
}
return null
}
val ucumService: UcumService by lazy { UcumServiceFactory.load() }

/*
* A "well-known" library name is one that is allowed to resolve without a
Expand Down Expand Up @@ -397,7 +375,6 @@ constructor(
}

companion object {
private val logger: Logger = LoggerFactory.getLogger(LibraryManager::class.java)
private val supportedContentTypes: Array<LibraryContentType> =
arrayOf(LibraryContentType.JSON, LibraryContentType.XML, LibraryContentType.CQL)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.cqframework.cql.cql2elm.ucum

import java.math.BigDecimal

interface UcumService {
/**
* Converts a quantity from one unit to another
*
* @param value the quantity to convert
* @param sourceUnit the unit of the quantity
* @param destUnit the unit to convert to
* @return the converted value in terms of the destination unit
*/
fun convert(value: BigDecimal, sourceUnit: String, destUnit: String): BigDecimal

/**
* Validate checks that a string is valid ucum unit
*
* @param unit
* @return null if valid, error message if invalid
*/
fun validate(unit: String): String?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.cqframework.cql.cql2elm.ucum

import java.util.*

object UcumServiceFactory {
fun load(): UcumService {
return ServiceLoader.load(UcumService::class.java).firstOrNull()
?: error(
"""No UCUM service implementation found.
Please ensure a UCUM service implementation is available on the classpath.
The 'ucum' module is a reference implementation that can be used for this purpose."""
)
}
}
1 change: 1 addition & 0 deletions Src/java/elm-jaxb/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ plugins {
dependencies {
api(project(":elm"))
testImplementation(project(":cql-to-elm"))
testImplementation(project(":ucum"))
testImplementation(project(":model-jaxb"))
}
1 change: 1 addition & 0 deletions Src/java/engine/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ plugins {
dependencies {
api(project(":elm"))
api(project(":cql-to-elm"))
api(project(":ucum"))
api("org.apache.commons:commons-text:1.10.0")

testImplementation(project(":model-jackson"))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package org.opencds.cqf.cql.engine.elm.executing;

import java.math.BigDecimal;
import org.fhir.ucum.Decimal;
import org.fhir.ucum.UcumService;
import org.cqframework.cql.cql2elm.ucum.UcumService;
import org.opencds.cqf.cql.engine.exception.InvalidOperatorArgument;
import org.opencds.cqf.cql.engine.runtime.Quantity;

Expand Down Expand Up @@ -37,13 +35,9 @@ public static Object convertQuantity(Object argument, Object unit, UcumService u
return null;
}
try {
Decimal result = ucumService.convert(
new Decimal(String.valueOf(((Quantity) argument).getValue())),
((Quantity) argument).getUnit(),
(String) unit);
return new Quantity()
.withValue(new BigDecimal(result.asDecimal()))
.withUnit((String) unit);
var result = ucumService.convert(
((Quantity) argument).getValue(), ((Quantity) argument).getUnit(), (String) unit);
return new Quantity().withValue(result).withUnit((String) unit);
} catch (Exception e) {
return null;
}
Expand Down
4 changes: 2 additions & 2 deletions Src/java/settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ include(
"cql-to-elm",
"cql-to-elm-cli",
"elm-fhir",
"ucum",
"tools:cql-formatter",
"tools:cql-parsetree",
"tools:xsd-to-modelinfo"
)


include("ucum")
8 changes: 8 additions & 0 deletions Src/java/ucum/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
plugins {
id("cql.library-conventions")
}

dependencies {
api(project(":cql-to-elm"))
api("org.fhir:ucum:1.0.8")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package org.cqframework.cql.ucum

import org.cqframework.cql.cql2elm.ucum.UcumService
import org.fhir.ucum.Decimal
import org.fhir.ucum.UcumEssenceService

@ConsistentCopyVisibility
data class DefaultUcumService private constructor(private val ucumService: UcumService) :
UcumService by ucumService {
JPercival marked this conversation as resolved.
Show resolved Hide resolved
constructor() : this(createUcumService())

companion object {
private fun createUcumService(): UcumService {
return try {
UcumEssenceService(
UcumEssenceService::class.java.getResourceAsStream("/ucum-essence.xml")
)
.let { u ->
object : UcumService {
override fun convert(
value: java.math.BigDecimal,
sourceUnit: String,
destUnit: String
): java.math.BigDecimal {
val ucumValue = Decimal(value.toString())
val converted = u.convert(ucumValue, sourceUnit, destUnit)
return java.math.BigDecimal(converted.asDecimal())
}

override fun validate(unit: String): String? {
return u.validate(unit)
}
}
}
} catch (e: org.fhir.ucum.UcumException) {
throw IllegalStateException(
"""Failed to create UCUM service.
Please ensure the 'ucum-essence.xml' file is available on the classpath.
The 'ucum' module is a reference implementation that can be used for this purpose.""",
e
)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.cqframework.cql.ucum.DefaultUcumService
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.cqframework.cql.ucum

import java.math.BigDecimal
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test

class DefaultUcumServiceTest {

@Test
fun testConvert() {
val ucumService = DefaultUcumService()
val result = ucumService.convert(BigDecimal("1"), "mg", "g")
assertEquals(BigDecimal("0.0010"), result)
}

@Test
fun testValidate() {
val ucumService = DefaultUcumService()
assertNull(ucumService.validate("mg"))
assertTrue(ucumService.validate("foo")?.contains("The unit 'foo' is unknown") ?: false)
}
}
Loading