Skip to content
Open
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.openmhealth.schema.configuration

import com.fasterxml.jackson.annotation.JsonInclude.Include
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.PropertyNamingStrategies
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.databind.node.JsonNodeFactory
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper

object JacksonConfiguration {

val objectMapper: ObjectMapper by lazy {
val objectMapper = jacksonObjectMapper()

// use snake case for names
objectMapper.propertyNamingStrategy = PropertyNamingStrategies.SNAKE_CASE

// serialize dates, date times, and times as strings, not numbers
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)

// don't serialize nulls
objectMapper.setSerializationInclusion(Include.NON_NULL)

// deserialize JSON numbers to Java BigDecimals
objectMapper.nodeFactory = JsonNodeFactory.withExactBigDecimals(true);
objectMapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)

// preserve time zone offsets when deserializing
objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)

// we default to the ISO8601 format for JSR-310
objectMapper.registerModule(JavaTimeModule())

objectMapper
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.openmhealth.schema.domain

import com.fasterxml.jackson.annotation.JsonValue

/**
* An interface implemented by Kotlin enumerations that represent schema enumerations.
*
* @author Emerson Farrugia
*/
interface SchemaEnumValue {
/**
* The schema enumeration value.
*/
@get:JsonValue
val schemaValue: String
}

inline fun <reified E> findBySchemaValue(schemaValue: String): E?
where E : SchemaEnumValue,
E : Enum<E> =
enumValues<E>()
.firstOrNull { it.schemaValue == schemaValue }
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.openmhealth.schema.domain

import java.util.regex.Pattern

/**
* A schema identifier. It consists of a namespace, a name, and a version. A schema identifier unambiguously identifies
* a single, immutable schema. The namespace is used to avoid naming collisions in schemas written by different groups
* or organisations.
*
* @author Emerson Farrugia
* @version 1.0
* @see [schema-id](https://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_schema-id)
*/
data class SchemaId(
val namespace: SchemaNamespace = OMH_NAMESPACE,
val name: SchemaName,
val version: SchemaVersion
) : Comparable<SchemaId> {

constructor(namespace: String, name: String, version: String) : this(
namespace = SchemaNamespace(namespace),
name = SchemaName(name),
version = SchemaVersion.fromString(version)
)

companion object {
val PATTERN: Pattern = Pattern.compile("([^:]+):([^:]+):([^:]+)")

fun fromString(id: String): SchemaId {
val matcher = PATTERN.matcher(id)
require(matcher.matches()) { "A malformed schema ID has been specified." }

return SchemaId(
namespace = matcher.group(1),
name = matcher.group(2),
version = matcher.group(3)
)
}

private val comparator = compareBy<SchemaId> { it.namespace }
.thenBy { it.name }
.thenBy { it.version }
}

override fun toString(): String = "$namespace:$name:$version"

override fun compareTo(other: SchemaId): Int =
comparator.compare(this, other)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.openmhealth.schema.domain

import java.util.regex.Pattern

/**
* A schema name, which uniquely [identifies][SchemaId] a schema when combined with a [SchemaNamespace] and [SchemaVersion].
*
* @author Emerson Farrugia
*/
@JvmInline
value class SchemaName(
val value: String
) : Comparable<SchemaName> {

init {
require(isValid(value)) { "A malformed name has been specified." }
}

companion object {
private const val PATTERN_STRING = "[a-zA-Z0-9-]+"
private val PATTERN: Pattern = Pattern.compile(PATTERN_STRING)

fun isValid(string: String?): Boolean =
string
?.let { PATTERN.matcher(it).matches() }
?: true
}

override fun toString(): String = value

override fun compareTo(other: SchemaName): Int =
value.compareTo(other.value)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.openmhealth.schema.domain

import java.util.regex.Pattern

val IEEE_NAMESPACE = SchemaNamespace("ieee")
val OMH_NAMESPACE = SchemaNamespace("omh")

/**
* A schema namespace, denoting a group or organisation that creates schemas.
*
* @author Emerson Farrugia
*/
@JvmInline
value class SchemaNamespace(
val value: String
) : Comparable<SchemaNamespace> {

init {
require(isValid(value)) { "A malformed namespace has been specified." }
}

companion object {
private const val PATTERN_STRING = "[a-zA-Z0-9.-]+"
private val PATTERN: Pattern = Pattern.compile(PATTERN_STRING)

fun isValid(string: String?): Boolean =
string
?.let { PATTERN.matcher(it).matches() }
?: true
}

override fun toString(): String = value

override fun compareTo(other: SchemaNamespace): Int =
value.compareTo(other.value)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.openmhealth.schema.domain

import com.fasterxml.jackson.annotation.JsonIgnore

/**
* An interface for schema classes.
*
* @author Emerson Farrugia
*/
interface SchemaSupport {

/**
* @return the schema this class corresponds to
*/
@get:JsonIgnore
val schemaId: SchemaId
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package org.openmhealth.schema.domain

import java.util.regex.Pattern

/**
* A semantic schema version, consisting of a major number, minor number, and an optional qualifier.
*
* @author Emerson Farrugia
*/
data class SchemaVersion(
val major: Int = 1,
val minor: Int = 0,
val qualifier: String? = null
) : Comparable<SchemaVersion> {

init {
require(major >= 0) { "A negative major version has been specified." }
require(minor >= 0) { "A negative minor version has been specified." }
require(qualifier == null || QUALIFIER_PATTERN.matcher(qualifier).matches()) {
"A malformed qualifier has been specified."
}
}

companion object {
private const val QUALIFIER_PATTERN_STRING = "[a-zA-Z0-9]+"
private const val PATTERN_STRING = "(\\d+)\\.(\\d+)(?:\\.($QUALIFIER_PATTERN_STRING))?"

val QUALIFIER_PATTERN: Pattern = Pattern.compile(QUALIFIER_PATTERN_STRING)
private val PATTERN: Pattern = Pattern.compile(PATTERN_STRING)

fun fromString(version: String): SchemaVersion {
val matcher = PATTERN.matcher(version)
require(matcher.matches()) { "A malformed version has been specified." }

return SchemaVersion(
major = Integer.valueOf(matcher.group(1)),
minor = Integer.valueOf(matcher.group(2)),
qualifier = matcher.group(3)
)
}

val comparator = compareBy<SchemaVersion> { it.major }
.thenBy { it.minor }
.thenBy { it.qualifier }
}

override fun toString(): String =
"$major.$minor" +
(qualifier
?.let { ".$it" }
?: "")

override fun compareTo(other: SchemaVersion): Int =
comparator.compare(this, other)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.openmhealth.schema.domain

import java.math.BigDecimal

data class TypedUnitValue<T : Unit>(
val unit: T,
val value: BigDecimal
) {
constructor(unit: T, value: Double) : this(unit, BigDecimal.valueOf(value))
constructor(unit: T, value: Long) : this(unit, BigDecimal.valueOf(value))
}
10 changes: 10 additions & 0 deletions kotlin-schema-sdk/bin/main/org/openmhealth/schema/domain/Unit.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.openmhealth.schema.domain

import org.openmhealth.schema.domain.SchemaEnumValue

/**
* A marker interface for units of measure.
*
* @author Emerson Farrugia
*/
interface Unit : SchemaEnumValue
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.openmhealth.schema.domain.ieee

import java.time.OffsetDateTime

/**
* A point in time.
*
* @author Emerson Farrugia
* @version 1.0
* @see [date-time][https://w3id.org/ieee/ieee-1752-schema/date-time.json]
*/
typealias DateTime = OffsetDateTime
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package org.openmhealth.schema.domain.ieee

import org.openmhealth.schema.domain.IEEE_NAMESPACE
import org.openmhealth.schema.domain.SchemaEnumValue
import org.openmhealth.schema.domain.SchemaId
import org.openmhealth.schema.domain.SchemaSupport

/**
* The descriptive statistic of a set of measurements. A measurement value can be the result of combining
* various measurements and calculating descriptive statistics like average, maximum, minimum, etc. Additional
* descriptive statistics will be added as the need arises. A measurement value without a descriptive statistic
* is interpreted as being the result of an individual measurement.
*
* @author Emerson Farrugia
* @version 1.0
* @see [descriptive-statistic][https://w3id.org/ieee/ieee-1752-schema/descriptive-statistic.json]
*/
enum class DescriptiveStatistic(
private val customSchemaValue: String? = null
) : SchemaEnumValue, SchemaSupport {

AVERAGE,
COUNT,
MAXIMUM,
MEDIAN,
MINIMUM,
STANDARD_DEVIATION,
SUM,
VARIANCE,
TWENTIETH_PERCENTILE("20th percentile"),
EIGHTIETH_PERCENTILE("80th percentile"),
LOWER_QUARTILE,
UPPER_QUARTILE,
QUARTILE_DEVIATION,
FIRST_QUINTILE("1st quintile"),
SECOND_QUINTILE("2nd quintile"),
THIRD_QUINTILE("3rd quintile"),
FOURTH_QUINTILE("4th quintile");

override val schemaValue: String
get() = customSchemaValue ?: name.lowercase().replace("_", " ")

override val schemaId: SchemaId
get() = SchemaId(IEEE_NAMESPACE.value, "descriptive-statistic", "1.0")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.openmhealth.schema.domain.ieee

import org.openmhealth.schema.domain.TypedUnitValue
import org.openmhealth.schema.domain.Unit

/**
* A unit of duration or length of time.
*
* @author Emerson Farrugia
* @version 1.0
* @see [duration-unit][https://w3id.org/ieee/ieee-1752-schema/duration-unit-value.json]
*/
enum class DurationUnit(
override val schemaValue: String
) : Unit {
PICOSECOND("ps"),
NANOSECOND("ns"),
MICROSECOND("us"),
MILLISECOND("ms"),
SECOND("sec"),
MINUTE("min"),
HOUR("h"),
DAY("d"),
WEEK("wk"),
MONTH("Mo"),
YEAR("yr");
}

typealias DurationUnitValue = TypedUnitValue<DurationUnit>
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.openmhealth.schema.domain.ieee

import com.fasterxml.jackson.annotation.JsonTypeInfo
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id

/**
* A time frame, which is either a point in time or a time interval.
*
* @author Emerson Farrugia
* @version 1.0
* @see [time-frame][https://w3id.org/ieee/ieee-1752-schema/time-frame.json]
*/
@JsonTypeInfo(use = Id.DEDUCTION)
sealed class TimeFrame

data class DateTimeTimeFrame(
val dateTime: DateTime
) : TimeFrame() {
companion object {

fun parse(text: String) =
DateTimeTimeFrame(DateTime.parse(text))
}
}

data class TimeIntervalTimeFrame(
val timeInterval: TimeInterval
) : TimeFrame()


Loading