BMM Persistence Syntax
Overview
A BMM schema is normally written in the ODIN syntax, although any other serialisation supporting typed object models may be used, including JSON (with type markers), YAML, XML etc. The ODIN form is described here. The structures are direct ODIN serialisations of the P_BMM_XXX classes in the persistence package.
Header Items
The following shows the header items of a BMM schema, which correspond to the 'persistent' attributes of the class P_BMM_SCHEMA.
------------------------------------------------------
-- P_BMM version on which these schemas are based.
------------------------------------------------------
bmm_version = <"2.3">
------------------------------------------------------
-- schema identification
-- (schema_id computed as <rm_publisher>_<schema_name>_<rm_release>)
------------------------------------------------------
rm_publisher = <"openehr">
schema_name = <"adltest">
rm_release = <"1.0.2">
model_name = <"TEST_PKG">
------------------------------------------------------
-- schema documentation
------------------------------------------------------
schema_revision = <"1.0.36">
schema_lifecycle_state = <"stable">
schema_description = <"openEHR schema to support test archetypes">
Inclusions
includes = <
["1"] = <
id = <"openehr_basic_types_1.0.2">
>
>
Package Definition
The packages definition should be self-explanatory: just name the classes and packages in a recursive fashion.
| only top-level package ids can be paths (i.e. contain the '.' character) |
| only classes defined in the same schema can be referenced in the package section in that schema. |
| make sure that the ODIN 'keys' are the same as the 'name' attributes in each block. |
packages = <
["org.openehr.test_pkg"] = <
name = <"org.openehr.test_pkg">
classes = <"WHOLE", "SOME_TYPE", "BOOK", "CHAPTER", "ENTRY", "CAR", "CAR_BODY">
>
>
Class Definitions
Classes for Primitive Types
Definitions for primitive types in a BMM schema are just normal class definitions within a primitive_types block. Types that are included here are usually types corresonding to primitives in target programming languages, XML schema or other downstream technologies. These types can be be detected as primitive types by tools, but otherwise are processed in the same way as types defined in the main class_definitions group.
unlike UML, all container types such as List<T>, Hash<V,K> etc are exlicit in a BMM schema, and consequently, such types are normally defined (including with generic parameters) in a BMM schema.
|
primitive_types = <
["Any"] = <
name = <"Any">
is_abstract = <True>
>
["Ordered"] = <
name = <"Ordered">
is_abstract = <True>
ancestors = <"Any">
>
>
Non-primitive Classes
The main group of class definitions in a schema occurs within the class_definitions block. Each definition is a keyed ODIN object block correspnding to a serialised P_BMM_CLASS object, where the key is the class name. Since name is a BMM meta-model attribute, the class definition always contains its ODIN key.
The possible class-level meta-properties:
-
name- class name - any capitalisation can be used, usually one of CamelCase or so-called UPPER_SNAKE_CASE; -
ancestors- states classes from which this class inherits, as an ODIN String list; -
is_abstract- indicates that the class cannot be instantiated directly; -
properties- ODIN block containing definitions consisting ofP_BMM_PROPERTYdescendants, keyed by property name.
Simple Classes
Simple classes are those whose type is the same as the class, as opposed to generic classes and enumerated types (see below).
class_definitions = <
["ITEM"] = <
name = <"ITEM">
ancestors = <"Any">
is_abstract = <True>
properties = <
-- properties here
>
>
-- more classes here
>
Class properties
Class properties from the original model are expressed using ODIN object blocks keyed by property name. Since there are multiple possible descendants of P_BMM_PROPERTY, ODIN type markers must be used to indicate which subtypes is used in each case.
The possible BMM meta-properties of all property types are as follows:
-
name-Stringname of the property in its owning class in the model - use camelCase or snake_case as appropriate; -
is_mandatory-Booleanflag indicating whether the property is mandatory within its class.
The following shows the class ELEMENT with two properties null_flavour and value of BMM meta-type P_BMM_SINGLE_PROPERTY, i.e. corresponding to a single-valued attribute from the original model.
["ELEMENT"] = <
name = <"ELEMENT">
ancestors = <"ITEM">
properties = <
["null_flavour"] = (P_BMM_SINGLE_PROPERTY) <
name = <"null_flavour">
type = <"DV_CODED_TEXT">
is_mandatory = <True>
>
["value"] = (P_BMM_SINGLE_PROPERTY) <
name = <"value">
type = <"DATA_VALUE">
>
>
>
Container Properties
The following is a P_BMM_CONTAINER_PROPERTY definition for the model property items: List<ITEM> in the ELEMENT class. The type is expressed in the type_def part which indicates the type of the container, which must be defined elsewhere in the schema, typically in the primitive types. The optional cardinality meta-property indicates cardinality of the container, and is expressed as a ODIN range. The default is |0..*|.
["ELEMENT"] = <
name = <"ELEMENT">
ancestors = <"ITEM">
properties = <
["items"] = (P_BMM_CONTAINER_PROPERTY) <
name = <"items">
type_def = <
container_type = <"List">
type = <"ITEM">
>
cardinality = <|>=1|>
is_mandatory = <True>
>
>
>
Indexed containers such as Hash<K,V>, Dictionary<K,V> etc are also suuported via the P_BMM_INDEXED_CONTAINER_PROPERTY, the use of which is shown here:
["CALLBACK_WAIT"] = <
name = <"CALLBACK_WAIT">
ancestors = <"...">
properties = <
["custom_actions"] = (P_BMM_INDEXED_CONTAINER_PROPERTY) <
name = <"custom_actions">
type_def = <
container_type = <"Hash">
index_type = <"String">
type = <"EVENT_ACTION">
>
cardinality = <|>=0|>
>
>
>
The above represents a property custom_actions: Hash <String, EVENT_ACTION> in the class CALLBACK_WAIT. The meta-element index_type is used to state the key type.
Generic Classes
Generic classes are those that have one or more substitutable generic type parameters. Such classes are therefore type generators, since actual types are formed by substitution of concrete types (typically simple classes) for the formal type parameters. The following example shows a generic class Interval with generic_parameter_defs of T which is constrained to conform to the type Ordered. This structure defineds the type Interval<T→Ordered>, with the same meaning as UML and programming languages supporting generic (templated) types.
Generic classes will normally contain one or more properties whose formal type is the generic type parameter, i.e. the T in this example, as is the case below where the model properties lower and upper are both declared to be of type T. This declaration necessitates the use of the BMM meta-type P_BMM_SINGLE_PROPERTY_OPEN.
["Interval"] = <
name = <"Interval">
ancestors = <"Any">
generic_parameter_defs = <
["T"] = <
name = <"T">
conforms_to_type = <"Ordered">
>
>
properties = <
["lower"] = (P_BMM_SINGLE_PROPERTY_OPEN) <
name = <"lower">
type = <"T">
>
["upper"] = (P_BMM_SINGLE_PROPERTY_OPEN) <
name = <"upper">
type = <"T">
>
>
>
Given the presence of generic classes in a BMM schema, derived generic types can be used as the type of properties in other classes, for which the BMM meta-type P_BMM_GENERIC_PROPERTY is used. The folowing example shows first a generic class DV_INTERVAL defined in a similar way to Interval above, and then a class SOME_TYPE containing the property qty_interval_attr whose model type is DV_INTERVAL<DV_QUANTITY>. In the latter type declaration, the DV_INTERVAL is the root_type and DV_INTERVAL the generic_parameter.
["DV_INTERVAL"] = <
name = <"DV_INTERVAL">
ancestors = <"Interval", "DATA_VALUE">
generic_parameter_defs = <
["T"] = <
name = <"T">
conforms_to_type = <"DV_ORDERED">
>
>
>
["SOME_TYPE"] = <
name = <"SOME_TYPE">
ancestors = <"Any", ...>
properties = <
["qty_interval_attr"] = (P_BMM_GENERIC_PROPERTY) <
name = <"qty_interval_attr">
type_def = <
root_type = <"DV_INTERVAL">
generic_parameters = <"DV_QUANTITY">
>
>
>
>
Type declarations can also be nested types, for example and container followed by a generic type. In the following the careProvider attribute is declared to be of model type List<Reference<Party>>. Any level of type nesting is allowed.
["Patient"] = <
name = <"Patient">
ancestors = <"Any">
properties = <
["careProvider"] = (P_BMM_CONTAINER_PROPERTY) <
name = <"careProvider">
type_def = <
container_type = <"List">
type_def = (P_BMM_GENERIC_TYPE) <
root_type = <"Reference">
generic_parameters = <"Party">
>
>
cardinality = <|>=0|>
>
>
>
The following property definition is based on the class REFERENCE_RANGE, and in this case, has a generic parameter type that is another generic type: DV_INTERVAL<DV_QUANTITY>. To express this, we use generic_parameter_defs instead of just generic_parameters to specify a type structure, rather than just a string type name. Note that generic_parameter_defs is a logical list in general, since there can always be more than one generic parameter, i.e. 'T', 'U' etc, even though it is most commonly just one. Accordingly, the usual ODIN keyed hash structure is used with each member being keyed by a generic parameter name, below ["T"].
["REFERENCE_RANGE"] = <
name = <"REFERENCE_RANGE">
ancestors = <"Any">
generic_parameter_defs = <
["T"] = <
name = <"T">
conforms_to_type = <"DV_ORDERED">
>
>
properties = <
["range"] = (P_BMM_SINGLE_PROPERTY) <
name = <"range">
type = <"DV_INTERVAL">
is_mandatory = <True>
>
>
>
["RANGE_OF_INTERVAL_OF_QUANTITY"] = <
name = <"RANGE_OF_INTERVAL_OF_QUANTITY">
ancestors = <"Any", ...>
properties = <
["range"] = (P_BMM_GENERIC_PROPERTY) <
name = <"range">
type_def = <
root_type = <"REFERENCE_RANGE">
generic_parameter_defs = <
["T"] = (P_BMM_GENERIC_TYPE) <
root_type = <"DV_INTERVAL">
generic_parameters = <"DV_QUANTITY">
>
>
>
>
>
The following example just does the same as the one above, but shows an (unrealistic) but legal case of multiple, mixed, nested generic parameters corresponding to the model property definition range: REFERENCE_RANGE<DV_INTERVAL<DV_QUANTITY>, Integer, List<DV_QUANTITY>, List<DV_INTERVAL<DV_QUANTITY>>>. The rules for expressing types is clearly illustrated:
-
use 'type' for simple string type refs; use
type_deffor structure types; -
within
P_BMM_GENERIC_TYPE, usegeneric_parametersfor a list of string types; -
use
generic_parameter_defsfor a list of complex type references.
["CRAZY_TYPE"] = <
name = <"CRAZY_TYPE">
ancestors = <"Any">
properties = <
["range"] = (P_BMM_GENERIC_PROPERTY) <
name = <"range">
type_def = <
root_type = <"REFERENCE_RANGE">
generic_parameter_defs = <
["T"] = (P_BMM_GENERIC_TYPE) <
root_type = <"DV_INTERVAL">
generic_parameters = <"DV_QUANTITY">
>
["U"] = (P_BMM_SIMPLE_TYPE) <
type = <"Integer">
>
["V"] = (P_BMM_CONTAINER_TYPE) <
type = <"DV_QUANTITY">
container_type = <"List">
>
["W"] = (P_BMM_CONTAINER_TYPE) <
type_def = (P_BMM_GENERIC_TYPE) <
root_type = <"DV_INTERVAL">
generic_parameters = <"DV_QUANTITY">
>
container_type = <"List">
>
>
>
>
>
>
Enumerated Types
In a BMM schema, enumerated types are treated as constrained forms of standard types with open ranges, currently only Integer and String. They are accordingly represented using class definitions of the meta-types P_BMM_ENUMERATION_INTEGER and P_BMM_ENUMERATION_STRING respectively. In either case, just names (items_names meta-property) or both names and values (item_values meta-property) can be specified.
The following example shows two variants of am enumerated type based on the Integer primitive type.
["PROPORTION_KIND"] = (P_BMM_ENUMERATION_INTEGER) <
name = <"PROPORTION_KIND">
ancestors = <"Integer">
item_names = <"pk_ratio", "pk_unitary", "pk_percent", "pk_fraction", "pk_integer_fraction">
>
["PROPORTION_KIND_2"] = (P_BMM_ENUMERATION_INTEGER) <
name = <"PROPORTION_KIND_2">
ancestors = <"Integer">
item_names = <"pk_ratio", "pk_unitary", "pk_percent", "pk_fraction", "pk_integer_fraction">
item_values = <0, 1001, 1002, 1003>
>
The following example shows two similar examples based on String.
["MAGNITUDE_STATUS"] = (P_BMM_ENUMERATION_STRING) <
name = <"MAGNITUDE_STATUS">
ancestors = <"String", ...>
item_names = <"le", "ge", "eq", "approx_eq">
item_values = <"<=", ">=", "=", "~">
>
["NAME_PART"] = (P_BMM_ENUMERATION_STRING) <
name = <"NAME_PART">
ancestors = <"String", ...>
item_names = <"FIRST", "MIDDLE", "LAST">
>
Value-set Constraints
Value-sets are related to enumerated types, but rather than specifying the possible instance values for a type in an explicit enumeration type, a standard type can be marked as having values that conform to an external value set, such as a terminology. The following shows an attribute declared to be of a simple type CODE_PHRASE in the normal way.
["encoding"] = (P_BMM_SINGLE_PROPERTY) <
name = <"encoding">
type = <"CODE_PHRASE">
>
With a value-set constraint added, it now looks as follows:
["encoding"] = (P_BMM_SINGLE_PROPERTY) <
name = <"encoding">
type_ref = <
type = <"CODE_PHRASE">
value_constraint = <"openEHR::languages">
>
>
The use of the value_constraint attribute forces the use of the type_ref structural form of the type definition within a P_BMM_SINGLE_PROPERTY instance, rather than the simple String form above.
Within a container type, the structure is slightly different, owing to another level of instance nesting. The following exmaple shows a container class declaration in original form and then with a value constraint.
-- original form
["language"] = (P_BMM_CONTAINER_PROPERTY) <
name = <"language">
type_def = <
container_type = <"List">
type = <"Coding">
>
>
-- with value constraint
["language"] = (P_BMM_CONTAINER_PROPERTY) <
name = <"language">
type_def = <
container_type = <"List">
type_def = (P_BMM_SIMPLE_TYPE) <
type = <"Coding">
value_constraint = <"hl7::Languages">
>
>
>
Inheritance
In the case of inheritance from simple classes, the ancestors list of Strings may be used to simply name the types (which are the same as class names), as seen in the above examples. In the case of generic inheritance, the ancestors are generic types, which may be open, partially closed or fully closed. The following example shows a class model containing generic inheritance in UML (using the closest approximation available in UML), followed by the equivalent P_BMM schema. In the latter, a structured ancestor_defs section is used instead of the ancestors String list, in the same way as for the P_BMM_GENERIC_PROPERTY examples above.
["GENERIC_PARENT"] = <
name = <"GENERIC_PARENT">
generic_parameter_defs = <
["T"] = <
name = <"T">
conforms_to_type = <"SUPPLIER">
>
["U"] = <
name = <"U">
conforms_to_type = <"SUPPLIER">
>
>
properties = <
["property_a"] = (P_BMM_SINGLE_PROPERTY_OPEN) <
name = <"property_a">
type = <"T">
>
["property_b"] = (P_BMM_SINGLE_PROPERTY_OPEN) <
name = <"property_b">
type = <"U">
>
>
>
["SUPPLIER"] = <
name = <"SUPPLIER">
is_abstract = <True>
properties = <
["abstract_prop"] = (P_BMM_SINGLE_PROPERTY) <
name = <"abstract_prop">
type = <"String">
>
>
>
["SUPPLIER_A"] = <
name = <"SUPPLIER_A">
ancestors = <"SUPPLIER">
properties = <
["magnitude"] = (P_BMM_SINGLE_PROPERTY) <
name = <"magnitude">
type = <"Double">
is_mandatory = <True>
>
["units"] = (P_BMM_SINGLE_PROPERTY) <
name = <"units">
type = <"String">
is_mandatory = <True>
>
>
>
["SUPPLIER_B"] = <
name = <"SUPPLIER_B">
ancestors = <"SUPPLIER">
properties = <
["property"] = (P_BMM_SINGLE_PROPERTY) <
name = <"property">
type = <"CODE_PHRASE">
is_mandatory = <True>
>
["precision"] = (P_BMM_SINGLE_PROPERTY) <
name = <"precision">
type = <"Integer">
>
>
>
["GENERIC_CHILD_OPEN_T"] = <
name = <"GENERIC_CHILD_OPEN_T">
ancestor_defs = <
["GENERIC_PARENT<T,SUPPLIER_B>"] = (P_BMM_GENERIC_TYPE) <
root_type = <"GENERIC_PARENT">
generic_parameters = <"T", "SUPPLIER_B">
>
>
generic_parameter_defs = <
["T"] = <
name = <"T">
conforms_to_type = <"SUPPLIER">
>
>
properties = <
["gen_child_open_t_prop"] = (P_BMM_SINGLE_PROPERTY) <
name = <"gen_child_open_t_prop">
type = <"String">
>
>
>
["GENERIC_CHILD_OPEN_U"] = <
name = <"GENERIC_CHILD_OPEN_U">
ancestor_defs = <
["GENERIC_PARENT<SUPPLIER_A,U>"] = (P_BMM_GENERIC_TYPE) <
root_type = <"GENERIC_PARENT">
generic_parameters = <"SUPPLIER_A", "U">
>
>
generic_parameter_defs = <
["U"] = <
name = <"U">
conforms_to_type = <"SUPPLIER">
>
>
properties = <
["gen_child_open_u_prop"] = (P_BMM_SINGLE_PROPERTY) <
name = <"gen_child_open_u_prop">
type = <"String">
>
>
>
["GENERIC_CHILD_CLOSED"] = <
name = <"GENERIC_CHILD_CLOSED">
ancestor_defs = <
["GENERIC_PARENT<SUPPLIER_A,SUPPLIER_B>"] = (P_BMM_GENERIC_TYPE) <
root_type = <"GENERIC_PARENT">
generic_parameters = <"SUPPLIER_A", "SUPPLIER_B">
>
>
properties = <
["gen_child_closed_prop"] = (P_BMM_SINGLE_PROPERTY) <
name = <"gen_child_closed_prop">
type = <"String">
>
>
>
>