Constraints Applied in Designs and Profiles

Not Balloting this Cycle
HL7 V3 CONSTRAINTS R1
HL7 Version 3 Constraints Applied in Designs and Profiles, Release 1
Last Ballot: For Comment Only - September 2005
Responsible Group Methodology & Modeling Work Group
HL7
Primary Contributor Grahame Grieve
Jiva Medical
Modeling & Methodology Co-Chair Woody Beeler
woody@beelers.com
Beeler Consulting LLC
Modeling & Methodology Co-Chair Ioana Singureanu
ioana.singureanu@med.va.gov
Eversolve / Patriot Tech
Modeling & Methodology Co-Chair Lloyd McKenzie
lloyd@lmckenzie.com
Lloyd McKenzie & Associates Consulting Ltd.
Modeling & Methodology Co-Chair Dale Nelson
dale@zed-logic.com
Zed-Logic Informatics, Inc.
Modeling & Methodology Co-Chair Craig Parker
craigparkermd@gmail.com
RemedyMD, Inc.

View Revision MarksHide Revision Marks

Table of Contents


Preface
    i Notes to Readers
    ii Known Issues & Planned Changes
Introduction
Constraints Catalog
    2.1 Summary
    2.2 [The content for this section is to be developed.]
Constraint Expression
    3.1 Use of OCL as a constraint language in RIM Derived Models
        3.1.1 Introduction
        3.1.2 Requirements
        3.1.3 Choice
        3.1.4 XPath-based constraints
        3.1.5 ADL
        3.1.6 Data Type Definition Language
        3.1.7 An Internal Language
        3.1.8 GELLO
        3.1.9 OCL
    3.2 Implementing OCL: Problems and Solutions
        3.2.1 Equality
        3.2.2 Null and NullFlavor
        3.2.3 OCL Typing syntax
        3.2.4 OCL Kernel Types
            3.2.4.1 Literals for Simple Types
            3.2.4.2 InFix operators
            3.2.4.3 Collections
            3.2.4.4 Collections Of Associations
        3.2.5 Generic Type Extensions
        3.2.6 Data Type Flavors
        3.2.7 Class Factory
        3.2.8 Utility Functions
        3.2.9 Terminologies
        3.2.10 Other things
        3.2.11 Types in Derived Models
        3.2.12 Association Names
        3.2.13 Dynamic Model binding
        3.2.14 Use of OCL for queries
    3.3 Author's Notes
    3.4 Use of OCL as an Expression Language
    3.5 Examples

For the September 2007 ballot cycle, the constraints materials contains new draft-for-comment content on the "Use of OCL as a constraint language in RIM Derived Models." This material is presented in Constraint Expression below.

This is an incomplete document. Methodology & Modeling has initiated a project to create a Version 3 standard on the assignement, and management of constraints. The Introduction summarizes the intended scope and lays out the three primary topics. This initial document includes one of those three topics and is published as a draft for comment.

This document did contain a section on Datatype flavors. This is now to be found within the specification "HL7 Version 3 Standard: Refinement, Constraint and Localization"

This document will be advanced through committee and membership normative ballots. Although M&M views this as a coherent set of topics, each of the topics will be managed separately, and may be offered at different times for balloting.


This chapter covers a set of topics, rules and processes related to the application of constraints when designing Version 3 artifacts.

 2.1Summary

The Constraints Catalog identifies and describes the rules or constraints that limit the kinds of model patterns that can be used in HL7 Version 3 models. (For example, a certain Participation can only be done by certain Roles played by specific kinds of Entities.) Many of these rules and constraints exist today in document and email archives, and will be collected and made available in this document.

HL7 V3 is based upon RIM Derived models. These models, such as DMIMs, RMIMS, CIMs, and LIMs, express structural and vocabulary constraints on the class models defined in the RIM and the datatypes. Within these models there is a requirement for additional constraints that the structural and vocabulary language are not able to express.

Since this need was first identified, a number of languauges have been evaluated, including OCL, the datatype definition language used in the abstract specifications, another HL7 defined language, ADL, and XPath-based constraints. For a variety of reasons OCL is the only workable choice.

This document describes the rationale for the use of OCL, and then describes how OCL can be used with the HL7 models. Due to the nature of the HL7 modeling process, as defined by the HL7 Development Framework, application of OCL to these models is not enabled by the standard definition of OCL, and additional bindings and information is required, both to OCL and to the HL7 classes. By providing these definitions, this document enables the use of OCL as a constraint langauge by HL7 in the RIM derived models.

The fundamental goal is to enable the use of constraints in RIM derived models. This leads to the following requirements:

  1. The language used should have a regular syntax and thorough semantic base

  2. The constraints should be in a computationally ready form

  3. The constraints should be meaningful to the modelers

  4. The constraints should be useful to the implementers

  5. The constraints should not be dependent on a particular implementation for the V3 models

  6. The should be tooling (or the prospect thereof) to help with authoring and enforcing the constraints

In practice, these requirements, particularly the last requirement, suggest that the language should be some kind of industry accepted language, but this is not a primary requirement.

 3.1.3Choice

When the need for a constraint language was first identified, a short list of candidates was drawn up, with OCL at the top of the list. Since the need was first identified, a number of candidates have been considered. Because there were issues with using OCL, other languages were evaluated.

Using XPath based constraints has the apparent attraction of being immediately applicable. The model constraints and an XML instance can be fed into a validation engine and the constraints checked. However, this attraction is only apparent. As soon as another model is derived from the model that expresses the constraints, and the derived model is used as the basis for the XML, the constraints may no longer be valid as associations are renamed in the derivation process.

Though this is a general problem with any solution and is discussed in depth below in [Section], it applies most of all to XPath since using this as a solution implies that the evaluation engine will have no knowledge of the model derivation heirarchy, which is somehow involved in any solution to this problem.

In addition, XPath is not a suitable language with which to engage with the functionality of the datatypes. Finally, XPath is inherently bound to a particular ITS, and would not be applicable to any other ITS.

 3.1.5ADL

ADL is a powerful constraint language that is defined by the openEHR foundation and has been adopted by CEN and ISO as a constraint language for use in healthcare specifications. While ADL is a powerful constraint language, most of the ADL features overlap with the existing static model language. For the constraints that the static model does not provide, ADL provides a constrained version of OCL. Not only does this not address the unresolved issues with OCL, it introduces problems with resolving the overlapping functionality or defining alternates to the ADL syntax to avoid overlapping.

There are also issues mapping ADL to the datatypes. The abstract datatypes are based on interfaces and properties, where as ADL is based on classes and attributes. Though some ADL tooling exists, ADL is not maintained by a recognised SDO, and the prospects of tooling made suitable for HL7 use from within or without of the openEHR group appear minimal.

The Data Type Definition Language (dtdl) is the language defined in the abstract data types specification, and is the language in which the datatypes themselves are defined. As a language which is primarily a modeling language, it is imminently suitable for expressing datatypes constraints, but it is not so clear as to it's suitability for constaints outside the scope of the datatypes.

However dtdl is not widely understood, and while it has a thorough semantic base, things are often expressed in a rather round about fashion in order to keep the semantics rigorous and the inbuilt features lean. These are important features for the abstract specification, but render the language unsuitable (and unwelcome) as a choice for HL7's constraint language. In addition, there is no tooling support for this language (and no particularly like to be either).

It would be possible for HL7 to define a language perfectly suited to the role. In fact, a candidate language for this has already been defined and reviewed. Though the language was imminently suitable for use in expressing the constraints, and clear and natural for the modelers, as non-standard, the language had no proven base and tooling support. For these reasons, it was decided to avoid this approach. (The language definition can be found at [url], and may find use in implementations, as it is easy to understand and not too hard to implement.)

 3.1.8GELLO

GELLO is effectively an adaptation of OCL for medical record querying. In the form that is defined, it offers nothing over plain OCL.

 3.1.9OCL

Although OCL does have some problems, it continues to be the best choice of the languages available. OCL is a well recognised standard supported by OMG, there is a number of commercial and open source implementations of tools for OCL, it is recognised standard for modeling constraints, and most OCL constraints are able to be executed by some sort of evaluation, or translated into executable code.

OCL is the best solution if the problems it poses can be resolved.

Using OCL in RIM derived models poses some problems. The problems primarily relate to the semantics of the datatypes and how types work in the derived models.

This section works through the problems, defining the problem and then defining how these problems are solved to enable use of OCL. The implementation of OCL against RIM derived problems relies on Data types R2, as several of the aspects of the solution require features defined in R2.

Note: There is also a UML ITS which solves the problem of allowing OCL use. However the UML ITS takes a rather different approach to solving the problems outlined in this document. Rather than adjusting the OCL environment to support the semantics and modeling approaches defined in the V3 standards, the UML ITS implements the V3 models within the context of a the normal OCL environment. This document takes the opposite approach in order to engage with the abstract models in their own terms.

Note: This specification assumes a single change to the definition of the classes and semantics defined by the datatypes and the RIM, which is that InfrastructureRoot is a specialisation of ANY and does not have nullFlavor as a property of it's own. Though RIM Classes are very different from datatypes in a number of ways, none of these differences are important to OCL, so this has no significant flow through effects. The grounds for this assumption are explained in [section], but it is important to note this here, so that it is clear that these definitions, such as when the meaning of equality is defined for ANY, apply to all RIM classes as well as the data types.

 3.2.1Equality

The definition of equality in OCL is "if self is the same object as object2". This differs from the definition of equals for HL7 objects, which excludes some properties from the definition of equality for an object, since two objects with different properties are not the same object. The HL7 definition is far more useful than the OCL one because it depends on the meaning of the objects rather than an implementation detail. The OCL definition does not discuss the (in fact most OCL users would be hard pressed to explain the difference because they mostly treat equals as if it behaved like the HL7 equals even though it doesn't).

Data types R2 defines a property on ANY called identical, which maps directly to the concept of equals in OCL. This is required in order to make constraints on uniqueness in Sets (DSETs in R2), because SET uniqueness constraints iterate the set twice, and enforce rules about the differences between any two different objects.

We could map OCL = to the HL7 identical routine and require that HL7 OCL users use .equal() rather than equal. However since the only use of the identity test in HL7 constraints in when specifying set uniqueness constraints, and the HL7 equals is otherwise appropriate, it is both more consistent and simpler for users if we map OCL = to the HL7 equal() property. Set uniqueness constraints must use .identical().

Note: Comment requested: We could define infix operator == for identical(), but my OCL engine reserves this for case insensitive comparison of strings, which is pretty useful, though the semantic basis for case insensitive comparison is weak].

In the abstract semantic model, there is really no such thing as a null object, only an object that is represented by a flavor of null. Though the word null is used in both contexts, there is a real difference in meaning.

In OCL, OclUndefined is the singleton instance of the type OclVoid which is a supertype of all other type. In the HL7 semantic model, the domain of nullFlavors is a generic domain extension of every other domain, including InfrastructureRoot itself.

For this reason, the OCL concept of null is not invoked when using OCL constraints in HL7 models. Specifically, instances of RIM classes or datatypes are never oclIsUndefined = true - they are always "Defined" in the OCL sense, though very often their nullFlavor will be NullFlavor.NI.

Note: One consequence of this is that care must be taken enforcing invariants inside an OCL evaluation engine. Although all the OCL constraints defined by this specification are executable, care must be taken to step in and stop executing the constraints on ANY itself, since these invariants never terminate because of the recursive nature of the definition of nullFlavor.

The OCL typing syntax is based on the UML Package syntax. The package names are as described in the MIF [reference!]. The rim and datatype packages are implicitly imported into every other V3 model. This means that any references to types defined in the RIM or the datatypes do not need to fully package qualified, just the simple name of the type can be used.

In OCL, datatypes are known by their shortname (the standard througout the rest of HL7 V3). In V3 generics are indicated using the syntax Type<Parameter>, which differs from the OCL syntax Type(Parameter). Both syntaxes are acceptable and have the same meaning.

The HL7 types defined in the RIM and v3 data types are properly defined types in the OCL sense, and they can be used as the types of parameters and variables. For instance, it makes sense to make the constraint Observation.value.oclisKindOf(REAL). x.oclIsKindOf(T) is a shortcut for saying x.dataType.imples(T). Therefore TYPE maps directly to the concept of type in OCL. When used against RIM derived models, any TYPE reference has the properties .shortName and .longName, though there doesn't appear to be any practical use for these.

The abstract datatypes derive all of the semantics from the definitions of ANY, TYPE, and BL. While this works really well in the abstract definitions, it presents a little bit of a problem when using OCL with the abstract datatypes. Specifically, OCL defines a set of prebuilt types calle the "Kernel Types" and these have features that are integrated with the syntax of OCL itself. The features of interest are:

  • Literal values for boolean, int, string and real
  • Infix operators
  • Iterators for collections

In the abstract datatypes, these classes are defined as specializations from ANY. In OCL, there are primitive types for Real, Integer, String, and Boolean, and there is special literal forms associated with them, though these are not well described.

There is two approaches to handling this with OCL. One approach is to arbitrarily declare that these literal forms are mapped directly to the equivalent HL7 types just like they are in the OCL declarations. The alternative is to define conversion functions between the OCL types and the HL7 types.

The second approach is conceptually more attractive, but difficult to define formally in practice. Take INT/Integer for an example. We could add an operation asInteger():Integer to the INT type, and also we can add the comparison operator =(x : Integer) : Boolean (we would also add <= etc). The problem with the = operation is that it must be symmetric. So if Integer i = INT j, then INT j = Integer i. Setting up the second is rather more difficult. Either we can postulate automatic type conversions, or we can annotate the existing OCL primitive type with operations such as =(i : INT) : Boolean. We need to be fairly consistent to do this in practice, as users are going to write constraints where they mix OCL primitives with their matching HL7 types. However in the end the OCL Primitives do not support the same semantics (nullFlavor) as the HL7 equivalents, and this is going to be a recipe for trouble.

Hence the first approach becomes more attractive, particularly when we consider the definition of an invariant: "An invariant constraint consists of an OCL expression of type Boolean." All HL7 invariants will end with a BL, and though we can add an operation like asBool() : Boolean, we don't want to have to have all user constraints ending in .AsBool(). We can more simply solve this by simply declaring that an invariant can return a type of BL. This is no longer compatible with a standard OCL execution environment, but we will have several other reasons for that anyway. And if we are going to start making rules like that, we might as well simply declare that when using OCL with HL7 RIM derived models, the OclPrimitive type names are actually aliases for the equivalent HL7 types. Given the addition of a few properties to the HL7 types (discussed below in [section]), this works seamlessly, and is only noticable to the implementers of the OCL engines.

As noted above, OCL does not define a specific format for the literals. When used in HL7 derived models, the OCL literal formats are those described by the literal forms defined in the abstract data types and we also extend the literal syntax by adding special definitions of the nullFlavors. NullFlavor values are specified by using the syntax NullFlavor.X where X is the code for the nullFlavor.

Note that the abstract datatypes defines equality of ST in terms of ED, and the mediatype and charset properties are included in the evaluation of equality. When string literals are defined, they have an implicit mediatype of text/plain, and the charset of the string literal is the charset of the OCL that defines the string literal. (Note that there is an open question before OMG concerning what character sets are acceptable for the OCL language. In lieu of a decision on this matter from OMG, all character sets are acceptable, though they do need to be able to render the HL7 type and property names correctly.)

The OCL standard defines that the operations '+', '-', '*'. '/', '<', '>', '<>' '<=' '>=' are used as infix operators. This means that instead of using the form a.+(b) = 3, the form a + b = 3 is allowed. This is a much more natural syntax for humans to work with.

Though this is not a feature that is totally necessary, it sure would be nice to have, and we can get this simply by declaring aliases between the HL7 operations and these infix operator symbols.

OCL Operator HL7 Operation
+ plus() (or concat() for ST)
- minus()
*' times()
/' dividedBy()
< lessThan()
> greaterThan
<> notEqual()
<= lessOrEqual()
>= greaterOrEqual()
implies implies()

The operation notEqual() will be described in [section]. In addition, the boolean operators not, or, and, xor, and implies map to those operations defined on the BL type. To be consistent, therefore, the infix operator implies can also be used for CD and specialisations, as in, Act.classCode implies ActClass.OBS.

 3.2.4.3Collections

OCL describes the use of collections in some detail, but doesn't clearly distinguish between generics and collections. Of the HL7 generics, Only DSET, LIST and BAG are collections. All the other generics are not collections. DSET, LIST and BAG all carry the stereotype "iterable" to denote this aspect of their behaviour. As a matter of convenience we also define an abstract type COLL, which is an additional generalization of these three types, and maps to the OCL Collection type. The three types will implement all the features of Collection, though some are mapped to functions of different names.

OCL defines a number of collection iterators, which are all variations of the common base iterator called iterate. Many useful constraints can be done with the iterators, and we'd like to have them available in OCL in the RIM derived models.

Aliasing the OCL types to the equivalent HL7 types, like with the primitives, seems like a reasonable course to follow, and with the addition of a few extra operations discussed in [Section], this works well for LIST, BAG and DSET. Note that the HL7 SET is not at all equivalent to the OCL Set and cannot be used with iterators as it is not necessarily collection based.

OCL defines the use of the notation -> to refer to properties of the collection such as size. This leaves the use of the dot notation to refer to the items in the collection. For instance, collection->size = 1 specifies that there is only one item in the collection, whereas collection.size = 1 specifies that every item in the collection has a property called size that is equal to 1 (this is an implicit use of the collect iterator). This notation is supported when OCL is used in RIM derived models: collection properties, including those properties defined on generalisation types ANY and SET, are accessed using the -> operator when the type is known to be a collection. If the collection is typecast to SET or ANY, then the same properties are accessed using the dot notation.

OCL 2 introduces the notion of nested collections, but does not describe their use. R2 types can be nested, and nested collections may be defined. Collection operations operate only at the first level of a collection. For instance, if coll has a type of LIST<LIST<INT>>, then coll.collect() returns a type of LIST<INT>, and coll->forAll(i | i = 1) would not be valid; instead, this would be valid: coll->forAll(nc | nc->forAll(i | i = 1)). The implicit syntax for collect is confusing when applied to nested collections and should not be used.

Associations are also implicitly collections. According to the OCL specification, Navigating an association will result in a Set, except when the association on the Class Diagram is adorned with {ordered}, in which case the navigation results in an OrderedSet.

Because of the way that equality is defined for HL7 objects, when RIM class associations are navigated, the result of the navigation is a BAG (note that HL7 does not allow for ordered associations - the property ActRelationship.sequenceNumber is used instead. Nor does HL7 allow for uniqueness between association targets).

The data types define the use of generic type extensions. These are generic types that extend their parameter rather than expressing properties of the type T (though they may do this as well). Generic Type Extensions are a challenge to implement (full implementation relies on Aspect Orientated Programming, and is outside this specification).

When OCL is used against RIM derived models, these generic type extensions work as defined in the data type specification. For instance, the value x of type PPD(REAL) has the property standardDeviation defined on PPD. In addition, x.oclIsKindOf(PPD) and x.oclisKindOf(REAL) are both true. Any class is a generic type extension if it carries the stereotype "mixin".

Flavors are named constraints on existing data types. Flavors can masquerade as data types but aren't true datatypes. For a value x of type CE.None, x.oclIsKindOf(CE), x.oclIsKindOf(CD), and x.oclIsKindOf(CE.None) are all true. What is different about flavors is that for x, x.oclIsTypeOf(CD) is true.

Note: In this sense, flavors are actually similar to generic type extensions. They behave rather similarly, but there is an alias for the combination. In the case above, CE.None is actually a generic type extension for CD, but instead of calling the combination CE.None(CD), it is simply called CE.None. Other than this aliasing, flavors are simply generic type extensions.

Note that for flavors, oclIsKindOf checks whether the type of the object is declared to be the flavor, not whether the flavors rules are actually met. To check that the instance conforms to a flavor, type cast the object to the flavor. If the result is non-Null, then the flavor's constraints are met. For example, if x has a type of CD, and a co-occurence constraint exists that under some circumstance y, x must be a CE.None, then this is the OCL to express this:

  inv: if y then x.oclAsType(CE.None)

When writing constraints, it is useful to be able to build datatypes in order to write useful constraints. It is generally true that any constraint an be expressed without needing to create any types, but these constraints can be rather long, indirect, and unwieldy. For this reason, we define a class factory that can create datatypes and RIM classes.

Note: It has been claimed that OCL is not able to create instances of classes, but this is not true. What OCL cannot do is change the existing system. Any classes created during the evaluation of OCL statements will cease to exist when the OCL statement evaluation is complete.

The class factory expresses one or more factory routines for each type or RIM class.

For datatypes, there may be routines to create the type from a literal, or routines to create the type that takes a specific list of parameters, or there may be a routine that takes a tuple.

All the RIM class factory methods take a tuple as a parameter. It must contain a named member for each attribute that should be populated on the class. Any attributes not represented in the tuple will be assigned a value of NullFlavor.NI. If the mandatory attributes are not represented in the tuple and they are not assigned a default value in the RIM, a partially valid object will be created, and it will have the NullFlavor OTH assigned as well as all the provided values.

   -- literal is abstract literal unless otherwise defined 
   BL - use literal
   ED new ED(Tuple t); -- named properties, including NullFlavor
   ST - use literal
   SC newSC(literal l, CV code );
   UID newUID(literal l);
   OID newOID(literal l);
   II newII(ST root[, ST extension[, CS scope, CS reliability]]]);
   TEL newTEL(literal l[, literal use[, GTS usablePeriod]]);
   EN newEN(literal l[, literal use[, IVL<TS> validTime]]);
   AD newAD(literal l[, literal use[, GTS usablePeriod]]);
   CD newCD(literal l); -- using terminology literal form defined below
   CD newCD(tuple); -- named properties (ones with no parameters), including NullFlavor
   CR newCR(CD name, CD value, BL inverted);
   CS newCS(literal l); -- using terminology literal form defined below
   CO newCO(literal l, INT v); -- using terminology literal form defined below
   TS newTS(literal l);
   INT - use literal
   REAL - use literal
   PQ newPQ(literal l);
   MO newMO(literal l);
   RTO newRTO(literal l);
   RTO newRTO(QTZ n, QTZ d);
   LIST newLIST(literal l);
   LIST newLIST([ANY i[, ANY i]n]);
   BAG newBAG(literal l);
   BAG newBAG([ANY i[, ANY i]n]);
   DSET newDSET(literal l);
   DSET newDSET([ANY i[, ANY i]n]);
   SLIST newSLIST(QTY origin, QTY scale, LIST<INT> digits);
   GLIST newGLIST(QTY inc, INT period, INT denominator);
   IVL newIVL(literal l);
   IVL newIVL(QTY q);
   IVL newIVL(QTY low, QTY high);
   PIVL newPIVL(literal l);
   EIVL newEIVL(literal l);
   QSD newQSD(QSET n1, QSET n2);
   QSI newQSI(DSET(QSET(ANY)) terms);
   QSU newQSU(DSET(QSET(ANY)) terms);
   HXIT newHXIT(ANY t, IVL<TS> validTime[, II controlAct]);
   PPD newPPD(ANY t, QTY stdDev, CS type);
   UVP newUVP(ANY t, REAL probability);
   
   -- you can also build DSET, LIST, and BAG using OCL collection syntax
   -- no constructors for flavors - just create a conformant instance of the real type

   Entity newEntity(Tuple t); 
   -- etc for every RIM class except infrastructureRoot
   
   -- if any of the parameters are null or have a nullFlavor, then the 
   -- Factory will construct a class of the desired type with the correct nullFlavor.

The types in the RIM and datatypes define only the properties required to clarify the semantic definitions of the types. There are a uumber of extra routines that are useful to complete the set of functions used in OCL.

This table summarises the OCL types and properties, and their equivalents in the V3 abstract datatypes. Where the types or properties have equivalents with different names, either name is acceptable and the meaning is the same in either case. However the V3 names are always preferred. Operation names in italics are defined in the abstract datatypes. Functions defined here adorn the V3 type within the OCL context. Their functionality is defined by the OCL specification.

--
OCL V3
OCLAny ANY
= equal
<> BL notEqual;
oclIsNew() ? not clear whether this is appropriate
oclIsUndefined() Technically this exists but it will always be false
oclIsInvalid() -- (not properly defined in OCL anyway)
oclAsType oclAsType
oclIsTypeOf oclIsTypeOf
oclIsKindOf oclIsKindOf
oclIsInState --
allInstances allInstances
Real REAL
+ plus
- minus
* times
/ dividedBy
abs REAL abs();
floor INT floor();
round INT round();
max REAL max(REAL x);
min REAL min(REAL x);
< lessThan
> greaterThan
<= lessOrEqual
>= greaterOrEqual
Integer INT
- minus
+ plus
* times
/ dividedBy
abs INT abs();
div dividedBy Is there a problem here?
mod remainder
max INT max(INT x);
min INT min(INT x);
String ST
size length
concat ST concat(ST x);
substring ST substring(INT low, INT high);
toInteger INT toInteger;
toReal REAL toReal;
Boolean BL
or or
xor xor
and and
not not
implies implies
Collection COLL
Sequence LIST
size length
includes contains
excludes BL excludes(T t);
count INT count(T t);
includesAll BL contains(COLL<T> c);
excludesAll BL excludes(COLL<T> c);
isEmpty isEmpty
notEmpty notEmpty
sum --
product --
union LIST<T> concat(LIST<T> x);
flatten --
append LIST<T> append(T t);
prepend LIST<T> prepend(T t);
insertAt LIST<T> insertAt(T t, INT i);
subSequence LIST<T> subList(INT low, INT high);
indexOf INT indexOf(T t);
at item
first T first;
last T last;
including append(T t);
excluding LIST<T> excluding(T t);
asBag BAG<T> asBag;
asSequence LIST<T> asList;
asSet SET<T> asSet;
asOrderedSet
Set DSET
size cardinality
includes contains
excludes BL excludes(T t);
count INT count(T t); (will be 1 or 0)
includesAll BL contains(COLL<T> c);
excludesAll BL excludes(COLL<T> c);
isEmpty isEmpty
notEmpty notEmpty
sum
product --
union union (+BAG<T> union(BAG<T> t);)
intersection(SET) intersection
intersection(BAG) SET<T> intersection(BAG<T> t);
- ?
including union
excluding except
symmetricDifference SET<T> symmetricDifference(SET(T) x);
flatten --
asSet SET<T> asSet;
asOrderedSet --
asSequence LIST<T> asList;
asBag BAG<T> asBag;
OrderedSet --
Bag BAG
size count
includes contains
excludes BL excludes(T t);
count INT count(T t);
includesAll BL contains(COLL<T> c);
excludesAll BL excludes(COLL<T> c);
isEmpty isEmpty
notEmpty notEmpty
sum --
product --
union(BAG) BAG<T> union(BAG<T> t);
union(SET) BAG<T> union(SET<T> t);
intersection BAG<T> intersection(BAG<T> t);
intersection BAG<T> intersection(SET<T> t);
including BAG<T> union(T t);
excluding BAG<T> except(T t);
flatten --
asBag BAG<T> asBag;
asSequence LIST<T> asList;
asSet SET<T> asSet;
asOrderedSet --

The following table defines additional decorations that are useful for making real world constraints.

Type Operation Definition
CD BL hasCodeSystem(OID x) true if this CD or one of it's translations comes from the specified codeSystem
CD BL hasImplication(CD x) true if this CD or one of it's translations implies x
CD BL implies(ST code; ST codeSystem) true if this CD or one of it's translations implies x
CD BL hasImplication(ST code; ST codeSystem) true if this CD or one of it's translations implies x

A number of the datatype properties and class attributes are bound to defined terminologies, or vocabulary domains. In the context of OCL, there is a class type for each vocabulary domain, and there is a one static method for each concept in the domain. The static method is identified by the code for the concept, and it returns a CD with the correct code, codeSystem, and codeSystemVersion populated. The displayName property will also be populated. In addition, each domain also has one extra method ST oid; which returns the oid of the code

As an example these constraints are valid:

  • Act.classCode implies ActClass.OBS - classCode must be OBS or a specialization thereof
  • Act.code.hasImplication(ActCode.DOSE) - somewhere there must be some code that implies a dose problem
 3.2.10Other things

Some features of OCL are not relevant when used with V3 models. This includes enumerations, messages, pre- and post-invariants, OclMessage, and OclInvalid. (This may change when we get further into dynamic models.)

allInstances is a property that is defined for use in HL7 V3 constraints, but constraints that use can generally not be evaluated, so users should avoid this if possible.

The Static Model diagrams represent a particular challenge to the use of OCL. OCL is a strongly typed language. Static models are widely regarded as Class Diagrams, and they do appear like class diagrams. The key to making the typing system work in this context is to understand that the Static models - other than the RIM and the data types - are not defining class models. This is not to say that there are not circumstances where it is useful to consider them as class models, but that in the context of OCL, we understand the static model diagrams to be visual declarations of constraint patterns on pre-existing RIM classes. The whole constraint methodology is geared towards ensuring that this is true, and it is possible to convert whole static models to OCL, which is not possible with genuine class models.

So static models are declarations of constraints on valid RIM graphs. The type of the object, when using oclIsKindOf, is the underlying RIM class type. The name on the "class" is the name of the constraint pattern. This sorts out the typing issue which is of great importance to OCL implementers but mainly an irritation for everyone else.

In order to make the OCL work in a practical fashion, we define a new binding technique for OCL (as allowed by the OCL standard itself). In this, we can associate constraints with a class that conforms to a pattern of constraints as named in an HL7 static model. So for this model:

Static Model Fragment

The formal constraint in OCL would be

context BirthPlace
  inv: birthplace.name.isNull implies (addr.count > 0 and not addr.isNull);

Actually the words "addr must be valued" are a little ambiguous. The OCL constraint is not ambiguous, which demonstrates the advantage of formal statements.

CMET references represent something of a problem, because of type names. Ideally, we'd like to refer to the classes in the CMETs - at the very least, the classes at the entry points, by simple names, rather than having to invoke by a full path name, since the full path name is rather long. We can do this by importing the CMET models into the static model, or at least asserting that this is assumed to be true. The problem with this is that there is no guarantee that the CMET class names will be unique. In the case of a name clash (if it is realised), then the fully qualified package name must be used to solve the ambiguity.

Todo.

Todo.

Todo.

At the end of this document, I am aware of a degree of irony here. Although we have stuck to the OCL syntax exactly, without any modification, the changes we have made to the OCL system library, with aliasing, subtle changes to the typing system, and the nullFlavor associated changes mean that standard OCL toolkits will not apply out of the box (An alternative approach can be pursued that doesn't have this problem - but it is recongiseably an ITS - the UML ITS). So we have lost the prime advantages of tooling support, but without a right hand turn in the way HL7 modeling works, we cannot do anything about this.

Most of these solutions are not as scary as they sound for implementors of OCL tools. While they do necessitate a custom tool, most tools will already have mapping tables, and these are mostly just different mapping tables. Some of the collection issues will be difficult, but OCL already makes some of these things difficult.

I have been keeping track of these issues and comparing them to my own OCL engine. In order to support this by providing syntax, semantic and type checking of OCL statements an also run-time validation of instances against the OCL statements, a number of changes are required to the engine. The most significant is actually the generic type extension/flavor changes, as the typing system runs deep in the engine. But it can be done (and if I do it, it will be open sourced through Eclipse OHF).

This section outlines the use of OCL as a language in the EXPR datatype.

The code for the language is "OCL".

The implicit context for the expression is the class that contains the attribute in which the expression is found. In addition there is an implicit definition:

context Class
  def: let result : T =

The correct type for T is subsituted in the implicit wrapper as appropriate. The actual contents of the expression is the source that would be needed after the =. For example, given this xml:

 <substanceAdministration>
   ...
   <maxDoseQuantity xsi:type="PQ">
	 <expression language="ocl">[?]</expression>
   </maxDoseQuantity>
   ...
   <derivedFrom>
     <localVariableName>bodyMass</localVariableName>
     <monitoringObservation>
       <code code="29463-7" codeSystem="2.16.840.1.113883.11.16492" displayName="BODY WEIGHT:MASS:PT:^PATIENT:QN"/>
       <value xsi:type="PQ" value="43" unit="kg"/>
     </monitoringObservation>
   </derivedFrom>
 </substanceAdministration>

The following OCL is valid as a content for the expression source:

	Factory.newPQ((derivedFrom->select(monitoringObservation.code.
	   hasImplication("29463-7", "2.16.840.1.113883.11.16492")).value.value * 2.3).literal+"mg/day");

Note that this highlights one of the features of the OCL as an expression language: it can only refer to data that is in the instance, as opposed to the simple factor form which is defined in the abstract specification. Obviously OCL is not suitable for use where these indirect references are required.

View Revision MarksHide Revision Marks Return to top of page