# Archie: openEHR Library
Archie is an openEHR Library written in Java, which can be used to implement openEHR tools and systems. See http://www.openehr.org for information about openEHR.
Archie works with the most recent versions of openEHR. This includes ADL version 2.
It contains an ADL 2 parser and an archetype object model implementation, as well as the EHR part of the reference model implementation.
It uses the ANTLR adl-grammar written by Thomas Beale at https://github.com/openehr/adl-antlr.
It is licensed under the Apache license.
## Migrating from the previous 0.3 version of archie
Version 0.4 of archie is a major update that requires changes in your code to upgrade. See https://github.com/openEHR/archie/wiki/Migration-from-Archie-0.3.x on how to migrate from archie version 0.3.x.
## Dependency
You can depend on parts of Archie, or the entire library at once. If you want the entire library, including the Archetype Object Model, ADL parser, all tools and openEHR reference model implementation, you can do in gradle:
```gradle
dependencies {
compile 'com.nedap.healthcare.archie:archie-all:4.0.0'
}
```
or if you use maven, in your pom.xml
```xml
com.nedap.healthcare.archiearchie-all4.0.0
```
If you want to depend on just the AOM and BMM, without any reference model implementation, depend on com.nedap.healthcare.archie:tools:4.0.0 and com.nedap.healthcare.archie:referencemodels:4.0.0 instead
## Build
[](https://travis-ci.org/openEHR/archie)
[](https://codecov.io/gh/openEHR/archie)
You need Java 17 or higher to build and run the library.
To build, Check out the source and type:
```sh
./gradlew build
```
To install to your local maven repository for use in other gradle or maven projects:
```sh
./gradlew publishToMavenLocal
```
## Contents
- [Usage](#usage)
* [Archetypes](#archetypes)
+ [Parsing and Archetype queries](#parsing-and-archetype-queries)
+ [Checking for parse errors](#checking-for-parse-errors)
+ [Creating an Archetype Repository](#creating-an-archetype-repository)
+ [Reference model metadata](#reference-model-metadata)
+ [Operational templates](#operational-templates)
+ [Terminology: texts, term bindings and descriptions for your archetypes](#terminology-texts-term-bindings-and-descriptions-for-your-archetypes)
+ [Diffing archetypes](#diffing-archetypes)
+ [Serializing to ADL](#serializing-to-adl)
+ [JSON and XML (de)serialization](#json-and-xml-deserialization)
+ [ODIN](#odin)
+ [Intervals and primitive objects](#intervals-and-primitive-objects)
+ [Archetype tree walking for openEHR reference models](#archetype-tree-walking-for-openehr-reference-models)
* [Reference Model tools](#reference-model-tools)
+ [Reference model object creation](#reference-model-object-creation)
+ [Reference model APath queries](#reference-model-apath-queries)
+ [Validating RM Objects against an archetype](#validating-rm-objects-against-an-archetype)
- [Experimental Features](#experimental-features)
* [Importing and converting ADL 1.4 files](#importing-and-converting-adl-14-files)
* [Full XPath support on reference model](#full-xpath-support-on-reference-model)
* [Rule evaluation](#rule-evaluation)
* [Lower level APIs](#lower-level-apis)
+ [Archetype Validation](#archetype-validation)
+ [Reference Model Metadata](#reference-model-metadata)
+ [Default constraints from the reference model](#default-constraints-from-the-reference-model)
+ [Use a different reference model implementation](#use-a-different-reference-model-implementation)
* [Status](#status)
* [Contributions](#contributions)
* [License](#license)
## Usage
## Archetypes
The base of openEHR are archetypes. Archie allows you to work with them, and defines tools to create and validate them.
### Parsing and Archetype queries
```java
ADLParser parser = new ADLParser();
Archetype archetype = parser.parse(adlFile);
AOMPathQuery query = new AOMPathQuery("/context[id1]/items[id2]/value");
ArchetypeModelObject object = query.find(archetype.getDefinition());
ArchetypeModelObject sameObject = archetype.getDefinition().itemAtPath("/context[id1]/items[id2]/value")
CAttribute attribute = archetype.getDefinition()
.getAttribute("context").getChild("id1").getAttribute("items");
```
Path segments must use node ids, or a one-based index such as `items[2]`. Matching a segment on the
meaning of a node, as in `/context[systolic]/items`, is no longer supported. If you need to find a node
by its term text, compare the meaning yourself:
```java
CObject systolic = archetype.getDefinition()
.getAttribute("context").getChildren().stream()
.filter(child -> "systolic".equalsIgnoreCase(child.getMeaning()))
.findFirst().orElse(null);
```
### Checking for parse errors
The parser is setup to continue parsing even if there are errors. This means you will manually have to check for parse errors in your code, after parsing each file:
```java
if(!parser.getErrors().hasNoErrors()) {
parser.getErrors().logToLogger();
}
```
### Creating an Archetype Repository
A single archetype can be useful, but archetypes often are related to other archetypes: They specialize a parent archetype, or link to another archetype. To easily use archetypes, you can create an ArchetypeRepository:
```java
InMemoryFullArchetypeRepository repository = new InMemoryFullArchetypeRepository();
for(Archetype archetype:allArchetypes) {
repository.addArchetype(archetype);
}
repository.compile(BuiltinReferenceModels.getMetaModelProvider());
for(ValidationResult result:repository.getAllValidationResults()) {
... your code here
}
```
The validation result contains the validation result, listing any errors in the archetype. It also contains the source archetype and if validation passed far enough to allow it to flatten, the flattened form of the archetype.
### Reference model metadata
You may have noticed a call to ```BuiltinReferenceModels.getMetaModelProvider()```. This retrieves the metadata for the reference models, which are needed to validate and flatten archetypes. Archie contains two types of metamodels: BMM, and reflection based metadata.
The BMM models are a file containing metadata in a form defined by the openEHR specifications.
The reflection based metadata contains ModelInfoLookup classes. They are derived from an implementation of a reference model. Note that the ModelInfoLookup classes are only added if you depended on them. If you depended on archie-all, you're all set.
### Operational templates
You can create operational templates of your archetypes. Think of operational templates as something you generate from an archetype or template to more easily work with it. If you are creating an EHR implementation, these will likely be the most useful form of archetypes to work with.
openEHR Archetypes allow you to reuse structures inside your archetypes at several places. It also allows to embed other archetypes inside your archetype. Without operational templates, you would need to build support for these structures into all of your tools. Operational templates fix this for you, by replacing the proxies for structure and embedded archetypes in the archetype with a copy of the actual embedded structure. For more information about operational templates, see (the documentation at the openEHR website)[http://openehr.org/releases/AM/latest/docs/OPT2/OPT2.html].
Note that ADL 2 operational templates is fundamentally different from the ADL 1.4 OET/OPT format. What you used to achieve with OET/OPT is now built into ADL 2 as templates in combination with the operational template creation. See (the openEHR specification on templates)[http://openehr.org/releases/AM/latest/docs/ADL2/ADL2.html#_templates] on how to work with them.
To create an Operational Template:
```java
Flattener flattener = new Flattener(repository, BuiltinReferenceModels.getMetaModelProvider()).createOperationalTemplate(true);
OperationalTemplate template = (OperationalTemplate) flattener.flatten(sourceArchetype);
```
Make sure you pass an original archetype, not a flattened version.
Note that a flattener is single use: to flatten another archetype after use, you will have to create another Flattener.
You can also setup the flattener to remove languages from the terminology by specifying the languages you wish to keep, by calling ```flattener.keepLanguages("en", "nl")```, with a list of language strings before flattening. Additionally you can also remove the languages from the metadata of the archetype by calling ```flattener.removeLanguagesFromMetadata(true)``` before flattening. Of course, if you leave out the ```flattener.createOperationalTemplate(true)``` call, the flattener will flatten the archetype, but not create an OperationalTemplate.
### Terminology: texts, term bindings and descriptions for your archetypes
You can directly use archetype.terminology() to get the meaning in any desired language. Archie contains convenience methods to make this easier for you in several ways. Since you likely will want to use the same language for many calls in the same thread, you can specify the language you are using as a thread local variable and just directly get terms for the Archetype Constraints.
```java
ArchieLanguageConfig.setThreadLocalMeaningAndDescriptionLanguage("nl");
CObject cobject = archetype.getDefinition().getAttribute("context").getChild("id1");
ArchetypeTerm term = cobject.getTerm();
logger.info("and the archetype term text in Dutch is: " + term.getText());
```
This just works in all cases - with the normal terminology of an unflattened archetype and with component terminologies from operational templates.
Of couse, you can also specify the language yourself:
```java
CObject cobject = archetype.getDefinition().getAttribute("context").getChild("id1");
ArchetypeTerm term = archetype.getTerm(cobject, "en");
logger.info("and the archetype term text in English is: " + term.getText());
```
Or you could access the terminology itself:
```java
archetype.getTerminology().getValueSets();
operationalTemplate.getComponentTerminologies()
```
For terminology constraints with locally defined values:
```java
CTerminologyCode cTerminologyCode = object.itemAtPath("/somePath...");
List = cTerminologyCode.getTerms();
```
or
```java
archetype.getTerm(cTerminologyCode, "at15", "en");
```
### Diffing archetypes
The inverse operation of flattening archetypes is diffing. This is useful if you write a tool to edit archetypes. For end users, it's much less complicated to edit flat forms of archetypes instead of differential forms. This operation lets users edit the flat form, after which you can save the output from the differentiator as the differential form. To use the Differentiator:
```java
Archetype flatChild, flatParent; //for how to parse and flatten, see elsewhere in the readme
Differentiator differentiator = new Differentiator(BuiltinReferenceModels.getMetaModelProvider());
Archetype diffed = differentiator.differentiate(flatChild, flatParent);
```
Use a flattened archetype both for the parent and the specialized archetype. Do not use OperationalTemplates.
The Differentiator supports differentiating the definition of the archetype, plus the terminology. It performs the following operations:
- adding sibling order markers to preserve the ordering of cObjects in the specialized archetype
- removing default or unspecialized cardinality, occurrences and existence
- removing unspecialized CObjects
- removing unspecialized CAttributes
- removing unspecialized tuples
- generating differential paths where possible
- generating a diff of the terminology, including term definitions, term bindings and value sets
- removes any translations that exist in the parent archetype, but not in the specialised archetype
- removing terminology extracts
It returns a new archetype as its result, the input archetypes are never altered.
The sibling order algorithm is written so that it creates a very small number of sibling order markers, and only generates them if necessary.
The diff operation does not diff any template overlays if presented with a Template. However, you can use a flattened TemplateOverlay plus its flat parent as arguments and obtain the differential form of the template overlay. You can then replace the edited template overlay in the template yourself. It is possible that this feature will be added in a later version.
It also does not yet diff the rules section, this must be manually performed.
### Serializing to ADL
Archetype models can be serialized to ADL thanks to the serializer written by @markopi64 from Marand Labs. To use:
```java
String serialized = ADLArchetypeSerializer.serialize(archetype);
```
This can be done on all kinds of Archetypes, including OperationalTemplates
### JSON and XML (de)serialization
Both the Archetype and Reference Model objects serialize and deserialize to JSON and XML. JSON serialization works using Jackson. You can get a fully configured ObjectMapper using JacksonUtil:
```java
String json = JacksonUtil.getObjectMapper().writeValueAsString(archetype);
Archetype parsedArchetype = JacksonUtil.getObjectMapper().readValue(json, Archetype.class);
```
The JSON output contains some features that make it easy to use in javascript, such as path attributes and translations in the language of your choice directly in the JSON. These features are ignored when parsing - as they should be.
XML works using JAXB. The XML output for the reference model conforms to the official openEHR RM XSD. For the AOM this is a bit tricky, because there is no finished XSD yet. It currently conforms to the development AOM 2.0 format. This format is still subject to change. To use:
```java
Marshaller marshaller = JAXBUtil.getArchieJAXBContext().createMarshaller();
StringWriter writer = new StringWriter();
marshaller.marshal(archetype, writer);
String xml = writer.toString();
Unmarshaller unmarshaller = JAXBUtil.getArchieJAXBContext().createUnmarshaller();
Archetype parsedArchetype = (Archetype) unmarshaller.unmarshal(new StringReader(xml));
```
### Jackson 3
The parsing, serializing and JSON features above can also be used with Jackson 3:
```java
ADLParser3 parser = new ADLParser3();
Archetype archetype = parser.parse(adlFile);
String serialized = ADLArchetypeSerializer.serializeWithJackson3(archetype, null, new ArchieRMObjectMapperProvider());
String json = JacksonUtil3.getObjectMapper().writeValueAsString(archetype);
Archetype parsedArchetype = JacksonUtil3.getObjectMapper().readValue(json, Archetype.class);
```
### ODIN
ODIN is a JSON/YAML like notation used as part of ADL, for meta-data, terminologies and annotations. See https://github.com/openehr/odin for what more it can do. To our knowledge it's not used widely outside of ADL/openEHR. Archie can map ODIN data directly to Java-objects using Jackson.
```java
YourType type = OdinObjectParser.convert(odinText, YourType.class);
```
It works by first converting to JSON, then binding that to objects with Jackson.
This means you can also convert ODIN to JSON.
```java
String json = new OdinToJsonConverter().convert(odinText);
```
The JSON conversion plus object mapping currently supports most ODIN features, except:
- Intervals of anything other than integers
- object references
To serialize your own object into ODIN, a Jackson object mapper is present. Note that unlike most Jackson Object Mappers, this mapper currently cannot parse ODIN, only serialize it. Trying to parse with it will result in exceptions. To use:
```java
ODINMapper mapper = new ODINMapper();
String result = mapper.writeValueAsString(yourObject);
```
Like any other Jackson ObjectMapper, many options in the mapping are configurable, such as custom type mapping.
### Intervals and primitive objects
The Interval has its ```has(value)``` method implemented. So you can do:
```java
new Interval<>(2,4).has(3); //returns true
Interval.upperUnbounded(2).has(1); //returns false
```
To check whether a value conforms to a CPrimitiveObject constraint, use the RMObjectValidator, or the
ValidationHelper it is built on:
```java
CString cString = new CString();
cString.addConstraint("test");
cString.addConstraint("/more te+st/");
ValidationHelper helper = new ValidationHelper(ArchieRMInfoLookup.getInstance(), new ValidationConfiguration.Builder().build());
helper.isValidValue(cString, "test"); //returns true
helper.isValidValue(cString, "more teeeeest"); //returns true - regexp matching
helper.isValidValue(cString, "mooooore test"); //returns false
```
ISO-8601 date constraint patterns are not yet implemented.
### Archetype tree walking for openEHR reference models
Because the getAttributes() has been implemented on the CObject-class instead of CComplexObject, creating a tree walker is just a few lines of code:
```java
public void walk(CObject cobject) {
for(CAttribute attribute:cobject.getAttributes()) {
for(CObject child:attributes.getChildren()) {
walk(child);
}
}
}
```
## Reference Model tools
Archie contains an implementation of the openEHR reference model version 1.1.0. It does not yet contain the task planning part. It also contains tools to work with reference model objects.
### Reference model object creation
The ```ExampleJsonInstanceGenerator``` generates valid JSON instances from Operational Templates. They can be parsed and used as examples of RM Object instances, or empty RM Object instances.
If you want to do this yourself, The RMObjectCreator creates empty reference model objects based on constraints. It can also set values based on attribute names. You can use it to create a reference model based on an archetype and user input. To create an empty reference model based on an archetype, you could work further on this example:
```java
public void createEmptyRMObject(CObject object) {
RMObjectCreator creator = new RMObjectCreator(ArchieRMInfoLookup.getInstance());
constructEmptyRMObject(ArchieRMInfoLookup.getInstance(), creator, cObject);
}
private RMObject constructEmptyRMObject(ModelInfoLookup lookup, RMObjectCreator creator, CObject object) {
RMObject result = creator.create(object);
for(CAttribute attribute: object.getAttributes()) {
List