-
Notifications
You must be signed in to change notification settings - Fork 156
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #313 from IBM/johntimm-master
FHIR Model Guide
- Loading branch information
Showing
8 changed files
with
831 additions
and
242 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
# FHIR Model Guide | ||
|
||
## Overview | ||
|
||
The FHIR model component provides Java APIs for parsing, building, generating and validating FHIR resources. Java model classes that represent FHIR resources and data types are generated directly from the structure definitions distributed with the spec. All model objects are thread-safe and immutable. Each model class implements the Java builder pattern (Effective Java, Joshua Bloch) and the visitor pattern (GoF). The classes also implement Java equals, hashCode and toString methods. All date/time processing is done using the Java 8 time library. | ||
|
||
Many of the data type classes include additional factory methods to facilitate object construction for common use cases. The model includes generated Javadoc comments complete with excerpts taken directly from the specification. Model classes also include Java annotations for constraints (`@Constraint`), required elements (`@Required`), choice element types (`@Choice`) and value set bindings (`@Binding`). Value set bindings are implemented using Code subclasses with constant fields and nested enumerations. Backbone elements are implemented as Java nested classes to keep them organized. | ||
|
||
All schema-level (structure, cardinality, value domain) and global (empty resource, empty element) constraint validation is happens during object construction. This means that it is virtually impossible to build a schema invalid FHIR resource using the APIs. Additional constraint validation (invariants, profile, terminology) is performed using the FHIRValidator class. FHIRParser and FHIRGenerator classes are used to parse and generate both JSON and XML formats. FHIRPathEvaluator is a FHIRPath evaluation engine built on an ANTLR4 generated parser. It implements are large portion of the FHIRPath specification and is used for validation and search parameter value extraction. | ||
|
||
## Building a Resource using the FHIR Model API | ||
|
||
The FHIR model API implements the Builder pattern for constructing Resource instances. | ||
|
||
``` | ||
Observation bodyWeight = Observation.builder() | ||
.meta(Meta.builder() | ||
.profile(Canonical.of("http://hl7.org/fhir/StructureDefinition/bodyweight")) | ||
.build()) | ||
.status(ObservationStatus.FINAL) | ||
.effective(DateTime.builder() | ||
.value("2019-01-01") | ||
.build()) | ||
.category(CodeableConcept.builder() | ||
.coding(Coding.builder() | ||
.system(Uri.of("http://terminology.hl7.org/CodeSystem/observation-category")) | ||
.code(Code.of("vital-signs")) | ||
.build()) | ||
.build()) | ||
.code(CodeableConcept.builder() | ||
.coding(Coding.builder() | ||
.system(Uri.of("http://loinc.org")) | ||
.code(Code.of("29463-7")) | ||
.build()) | ||
.build()) | ||
.value(Quantity.builder() | ||
.value(Decimal.of(200)) | ||
.system(Uri.of("http://unitsofmeasure.org")) | ||
.code(Code.of("[lb_av]")) | ||
.unit(string("lbs")) | ||
.build()) | ||
.build(); | ||
``` | ||
|
||
In the example above, a number of different builder classes are used: | ||
|
||
- `Observation.Builder` | ||
- `DateTime.Builder` | ||
- `CodeableConcept.Builder` | ||
- `Quantity.Builder` | ||
|
||
Every type in the model that represents a FHIR resource or element has a corresponding nested, static Builder class used for constructing thread-safe, immutable instances. | ||
|
||
Several static factory / utility methods are also used: | ||
|
||
- `Canonical.of(...)` | ||
- `Uri.of(...)` | ||
- `Code.of(...)` | ||
- `String.string(...)` (via static import) | ||
|
||
Many of the primitive data types contain this type of "helper" method. | ||
|
||
Fields from an immutable model object may be copied back into a builder object using the `toBuilder()` method: | ||
``` | ||
bodyWeight = bodyWeight.toBuilder() | ||
.value(bodyWeight.getValue().as(Quantity.class).toBuilder() | ||
.value(Decimal.of(210)) | ||
.build()) | ||
.build(); | ||
``` | ||
|
||
## Parsing a Resource from an InputStream or Reader | ||
|
||
``` | ||
// Parse from InputStream | ||
InputStream in = getInputStream("JSON/bodyweight.json"); | ||
Observation observation = FHIRParser.parser(Format.JSON).parse(in); | ||
// Parse from Reader | ||
Reader reader = getReader("JSON/bodyweight.json"); | ||
Observation observation = FHIRParser.parser(Format.JSON).parse(reader); | ||
``` | ||
|
||
## Generating JSON and XML formats from a Resource instance | ||
|
||
``` | ||
// Generate JSON format | ||
FHIRGenerator.generator(Format.JSON).generate(bodyWeight, System.out); | ||
// Generate XML format | ||
FHIRGenerator.generator(Format.XML).generate(bodyWeight, System.out); | ||
``` | ||
|
||
The `FHIRGenerator` interface has a separate factory method that takes `boolean prettyPrinting` as a parameter: | ||
|
||
``` | ||
// Generate JSON format (with pretty printing) | ||
FHIRGenerator.generator(Format.JSON, true).generate(bodyWeight, System.out); | ||
``` | ||
|
||
## Validating a Resource instance | ||
|
||
Schema-level validation occurs during object construction. This includes validation of cardinality constraints and value domains. Additional validation of constraints specified in the model is performed using the `FHIRValidator` class. | ||
|
||
``` | ||
Observation observation = getObservation(); | ||
List<Issue> issues = FHIRValidator.validator().validate(observation); | ||
for (Issue issue : issues) { | ||
if (IssueSeverity.ERROR.equals(issue.getSeverity())) { | ||
// handle error | ||
} | ||
} | ||
``` | ||
|
||
## Evaluating FHIRPath expressions on a Resource instance | ||
|
||
``` | ||
EvaluationContext evaluationContext = new EvaluationContext(bodyWeight); | ||
Collection<FHIRPathNode> result = FHIRPathEvaluator.evaluator().evaluate(evaluationContext, "Observation.value.as(Quantity).value >= 200"); | ||
assert(FHIRPathUtil.isTrue(result)); | ||
``` | ||
|
||
The `EvaluationContext` class builds a `FHIRPathTree` from a FHIR resource or element. A `FHIRPathTree` is a tree of labeled nodes that wrap FHIR elements and are used by the FHIRPath evaluation engine (`FHIRPathEvaluator`). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
69 changes: 69 additions & 0 deletions
69
fhir-model/src/test/java/com/ibm/fhir/model/test/FoodIntakeObservationTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
/* | ||
* (C) Copyright IBM Corp. 2019 | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package com.ibm.fhir.model.test; | ||
|
||
import static com.ibm.fhir.model.type.String.string; | ||
|
||
import com.ibm.fhir.model.format.Format; | ||
import com.ibm.fhir.model.generator.FHIRGenerator; | ||
import com.ibm.fhir.model.resource.Observation; | ||
import com.ibm.fhir.model.resource.Observation.Component; | ||
import com.ibm.fhir.model.type.Code; | ||
import com.ibm.fhir.model.type.CodeableConcept; | ||
import com.ibm.fhir.model.type.Coding; | ||
import com.ibm.fhir.model.type.Decimal; | ||
import com.ibm.fhir.model.type.Element; | ||
import com.ibm.fhir.model.type.Quantity; | ||
import com.ibm.fhir.model.type.Uri; | ||
import com.ibm.fhir.model.type.code.ObservationStatus; | ||
|
||
public class FoodIntakeObservationTest { | ||
private static final String SYSTEM_SNOMED_CT = "http://snomed.info/sct"; | ||
private static final String SYSTEM_UNITS_OF_MEASURE = "http://www.unitsofmeasure.org"; | ||
|
||
public static void main(String[] args) throws Exception { | ||
Observation foodIntakeObservation = Observation.builder() | ||
.status(ObservationStatus.FINAL) | ||
.code(codeableConcept(coding(SYSTEM_SNOMED_CT, "226379006", "Food intake"))) | ||
.component(component(codeableConcept(coding(SYSTEM_SNOMED_CT, "226441002", "Fish intake")), | ||
quantity(250, "grams", SYSTEM_UNITS_OF_MEASURE, "g"))) | ||
.component(component(codeableConcept(coding(SYSTEM_SNOMED_CT, "226404003", "Milk intake")), | ||
quantity(1, "cup", SYSTEM_UNITS_OF_MEASURE, "[cup_us]"))) | ||
.build(); | ||
FHIRGenerator.generator(Format.JSON, true).generate(foodIntakeObservation, System.out); | ||
} | ||
|
||
public static Component component(CodeableConcept code, Element value) { | ||
return Component.builder() | ||
.code(code) | ||
.value(value) | ||
.build(); | ||
} | ||
|
||
public static CodeableConcept codeableConcept(Coding... coding) { | ||
return CodeableConcept.builder() | ||
.coding(coding) | ||
.build(); | ||
} | ||
|
||
public static Coding coding(String system, String code, String display) { | ||
return Coding.builder() | ||
.system(Uri.of(system)) | ||
.code(Code.of(code)) | ||
.display(string(display)) | ||
.build(); | ||
} | ||
|
||
public static Quantity quantity(Number value, String unit, String system, String code) { | ||
return Quantity.builder() | ||
.value(Decimal.of(value)) | ||
.unit(string(unit)) | ||
.system(Uri.of(system)) | ||
.code(Code.of(code)) | ||
.build(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.