HAPI FHIR notes

Russell Bateman
February 2020
last update:





Finally getting around to starting a page of notes on using HAPI-FHIR.

Links


HAPI FHIR: a simple round trip...
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.IParser;

import org.hl7.fhir.r5.model.Bundle;

public class HapiFhirRoundTrip
{
  private static final String  PATIENT_AND_ENCOUNTER =
    + "<Bundle xmlns=\"http://hl7.org/fhir\">\n"
    + "  <type value=\"transaction\" />\n"
    + "  <entry>\n"
    + "    <fullUrl value=\"urn:uuid:38826a3f-8e5c-4846-95c3-7f12a233447d\" />\n"
    + "    <resource>\n"
    + "      <Patient xmlns=\"http://hl7.org/fhir\">\n"
    + "        <meta>\n"
    + "          <lastUpdated value=\"2021-01-05T00:00:00.000-06:00\" />\n"
    + "        </meta>\n"
    + "        <identifier>\n"
    + "          <system value=\"https://fhir.acme.io/facility/Beverly Hills Clinic\" />\n"
    + "          <value value=\"3660665800\" />\n"
    + "          <assigner>\n"
    + "            <display value=\"Beverly Hills Clinic\" />\n"
    + "          </assigner>\n"
    + "        </identifier>\n"
    + "        <name>\n"
    + "          <family value=\"Munster\" />\n"
    + "          <given value=\"Herman\" />\n"
    + "        </name>\n"
    + "        <gender value=\"male\" />\n"
    + "        <birthDate value=\"1835-10-31\" />\n"
    + "        <deceasedBoolean value=\"false\" />\n"
    + "        <address>\n"
    + "          <text value=\"1313 Mockingbird Lane, Mockingbird Heights, CA 90210\" />\n"
    + "          <line value=\"1313 Mockingbird Lane\" />\n"
    + "          <city value=\"Mockingbird Heights\" />\n"
    + "          <state value=\"CA\" />\n"
    + "          <postalCode value=\"90210\" />\n"
    + "        </address>\n"
    + "        <telecom>\n"
    + "          <system value=\"phone\" />\n"
    + "          <value value=\"+3035551212\" />\n"
    + "          <use value=\"home\" />\n"
    + "        </telecom>\n"
    + "      </Patient>\n"
    + "    </resource>\n"
    + "  </entry>\n"
    + "  <entry>\n"
    + "    <resource>\n"
    + "      <Encounter xmlns=\"http://hl7.org/fhir\">\n"
    + "        <id value=\"emerg\" />\n"
    + "        <period>\n"
    + "          <start value=\"2017-02-01T08:45:00+10:00\" />\n"
    + "          <end value=\"2017-02-01T09:27:00+10:00\" />\n"
    + "        </period>\n"
    + "      </Encounter>\n"
    + "    </resource>\n"
    + "    <request>\n"
    + "      <method value=\"POST\" />\n"
    + "      <url value=\"Patient\" />\n"
    + "    </request>\n"
    + "  </entry>\n"
    + "</Bundle>\n";

  @Test
  public void test()
  {
    FhirContext context = FhirContext.forR5();
    IParser     parser  = context.newXmlParser().setPrettyPrint( true );
    Bundle      bundle  = ( Bundle ) parser.parseResource( PATIENT_AND_ENCOUNTER );
    System.out.println( parser.encodeResourceToString(bundle ) );
  }
}

Output

<Bundle xmlns="http://hl7.org/fhir">
   <type value="transaction"/>
   <entry>
      <fullUrl value="urn:uuid:38826a3f-8e5c-4846-95c3-7f12a233447d"/>
      <resource>
         <Patient xmlns="http://hl7.org/fhir">
            <meta>
               <lastUpdated value="2021-01-05T00:00:00.000-06:00"/>
            </meta>
            <identifier>
               <system value="https://fhir.acme.io/facility/Beverly Hills Clinic"/>
               <value value="3660665800"/>
               <assigner>
                  <display value="Beverly Hills Clinic"/>
               </assigner>
            </identifier>
            <name>
               <family value="Munster"/>
               <given value="Herman"/>
            </name>
            <telecom>
               <system value="phone"/>
               <value value="+3035551212"/>
               <use value="home"/>
            </telecom>
            <gender value="male"/>
            <birthDate value="1835-10-31"/>
            <deceasedBoolean value="false"/>
            <address>
               <text value="1313 Mockingbird Lane, Mockingbird Heights, CA 90210"/>
               <line value="1313 Mockingbird Lane"/>
               <city value="Mockingbird Heights"/>
               <state value="CA"/>
               <postalCode value="90210"/>
            </address>
         </Patient>
      </resource>
   </entry>
   <entry>
      <resource>
         <Encounter xmlns="http://hl7.org/fhir">
            <id value="emerg"/>
         </Encounter>
      </resource>
      <request>
         <method value="POST"/>
         <url value="Patient"/>
      </request>
   </entry>
</Bundle>

Time out for FhirPath...

Of course, we know how to parse patient data out of a FHIR document:

  private static final String PATIENT = ""
      +  "<Patient xmlns=\"http://hl7.org/fhir\">\n"
      +  "  <id value=\"9.PI\" />\n"
      +  "  <meta>\n"
      +  "    <lastUpdated value=\"2016-04-22T00:00:00.000-06:00\" />\n"
      +  "  </meta>\n"
      +  "  <name>\n"
      +  "    <family value=\"Munster\" />\n"
      +  "    <given value=\"Herman\" />\n"
      +  "  </name>\n"
      +  "  <gender value=\"male\" />\n"
      +  "  <birthDate value=\"1835-10-31\" />\n"
      +  "  <address>\n"
      +  "    <text value=\"1313 Mockingbird Lane, Mockingbird Heights, Beverly Hills, CA 90210\" />\n"
      +  "    <line value=\"1313 Mockingbird Lane\" />\n"
      +  "    <city value=\"Beverly Hills\" />\n"
      +  "    <state value=\"CA\"/>\n"
      +  "    <postalCode value=\"90210\" />\n"
      +  "  </address>\n"
      +  "</Patient>";

First, straightforwardly using HAPI FHIR

  @Test
  public void testStraight()
  {
    long start = System.currentTimeMillis();

    final FhirContext  context    = FhirContext.forR4();
    final IParser      parser     = context.newXmlParser();
    final Patient      patient    = parser.parseResource( Patient.class, new ByteArrayInputStream( PATIENT.getBytes() ) );

    for( HumanName name : patient.getName() )
      System.out.println( name.getFamily() );

    for( HumanName name : patient.getName() )
      System.out.println( name.getFamily() + ", " + name.getGivenAsSingleString() );

    System.out.println( patient.getBirthDate().toString() );

    System.out.println( patient.getAddress().get( 0 ).getPostalCode() );

    System.out.println( patient.getId() );

    long end = System.currentTimeMillis();
    System.out.println( "Time to run: " + ( end - start ) + " milliseconds." );
  }

  /* output:
    Munster
    Munster, Herman
    Sat Oct 31 00:00:00 MST 1835
    90210
    Time to run: 38 milliseconds.
   */

However!

...it's also possible to do it using FHIRPath


  @Test
  public void testUsingFHIRPath()
  {
    long start = System.currentTimeMillis();

    final FhirContext  context    = FhirContext.forR4();
    final IParser      parser     = context.newXmlParser();
    final Patient      patient    = parser.parseResource( Patient.class, new ByteArrayInputStream( PATIENT.getBytes() ) );

    final String       familyPath = "Patient.name.family";
    List< StringType > family     = context.newFhirPath().evaluate( patient, familyPath, StringType.class );
    for( StringType f : family )
      System.out.println( f );

    final String       namePath = "Patient.name";
    List< HumanName >  name     = context.newFhirPath().evaluate( patient, namePath, HumanName.class );
    for( HumanName n : name )
      System.out.println( n.getFamily() + ", " + n.getGivenAsSingleString() );

    final String       dobPath = "Patient.birthDate.value";
    List< StringType > dob     = context.newFhirPath().evaluate( patient, dobPath, StringType.class );
    for( StringType d : dob )
      System.out.println( d );

    final String       zipPath = "Patient.address.postalCode";
    List< StringType > zip     = context.newFhirPath().evaluate( patient, zipPath, StringType.class );
    for( StringType z : zip )
      System.out.println( z );

    final String           idPath = "Patient.id.value";
    Optional< StringType > id     = context.newFhirPath().evaluateFirst( patient, idPath, StringType.class );
    //noinspection OptionalGetWithoutIsPresent
    System.out.println( id.get() );

    long end = System.currentTimeMillis();
    System.out.println( "Time to run: " + ( end - start ) + " milliseconds." );
  }

  /* output:
    Munster
    Munster, Herman
    1835-10-31
    90210
    Time to run: 545 milliseconds.
   */

Notes on   evaluate( IBase, String, Class ):

  1. the first argument (lines 11, 16, 21, 26, 31) is the parsed FHIR content (from line 8).
  2. the second argument is the FHIR path we want (lines 10, 15, 20, 25, 30). Note, however, that there are FHIR paths potentially way more complex than these.
  3. the third argument is the type of the response to be returned. It could simply be IBase.class, the ancestor of StringType, but I made it more explicit. Whatever the case, it must match the type of the variable about to collect it.
  4.  
  5. evaluate() returns a list of everything that matches the second argument. (As illustrated in our examples, the list may be only 1 deep, but this is only the case because of my pawltry Patient.)
  6. evaluateFirst() returns the first thing that matches the second argument. As noted, in our examples, there was only one thing that matched, so the result is the same. I used this nevertheless in the last example for completeness.
  7. I don't have much of a benchmark, but what I do have plus the lag-time in the debugger tells me that using FHIRPath is a very expensive way to tear apart a FHIR document. It's much faster going to use the direct approach (14×) if you're doing lots of parsing while HAPI FHIR is holding it in its hand.
  8.  
  9. The reason for HAPI FHIR FHIRPathe rather than simply XPath is because FHIR can be expressed several formats, like JSON, and not just XML.

Here are the imports for the two tests above:

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.List;
import java.util.Optional;

import org.junit.Test;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.parser.IParser;

import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext;
import org.hl7.fhir.r4.model.Base;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Encounter;
import org.hl7.fhir.r4.model.HumanName;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.utils.FHIRPathEngine;

import com.windofkeltia.utilities.TestUtilities;

The FHIRPath cookie jar—everything you need!

Here's an exhaustive sample in Java to find a FHIR Patient resource in a FHIR document (whose contents have already been read into variable CONTENTS) using FHIRPath. This includes priming the pump for the HAPI FHIR parser, reading in an parsing the FHIR document (usually a Bundle), priming the pump for the FHIRPath component and extracting a Patient resource from the FHIR document using a FHIRPath.

// priming the HAPI FHIR parser:
FhirContext                     context    = FhirContext.forR5();
IParser                         parser     = context.newXmlParser();

// reading the FHIR document into a resource:
BaseResource                    bundle     = parser.parseResource( CONTENT.getBytes() );

// priming the pump for the FHIRPath component:
DefaultProfileValidationSupport validation = new DefaultProfileValidationSupport( context );
IWorkerContext                  worker     = new HapiWorkerContext( context, validation );
FHIRPathEngine                  fhirEngine = new FHIRPathEngine( worker );

// extracting a Patient resource from the FHIR document using FHIRPath:
List< Base >                    patients   = fhirEngine.evaluate( bundle, "Bundle.entry.resource.ofType(Patient)" ); *
Patient                         patient    = ( Patient ) patients.get( 0 );

For quick copy and paste:

FhirContext                     context    = FhirContext.forR5();
IParser                         parser     = context.newXmlParser();
BaseResource                    bundle     = parser.parseResource( CONTENT.getBytes() );
DefaultProfileValidationSupport validation = new DefaultProfileValidationSupport( context );
IWorkerContext                  worker     = new HapiWorkerContext( context, validation );
FHIRPathEngine                  fhirEngine = new FHIRPathEngine( worker );
List< Base >                    patients   = fhirEngine.evaluate( bundle, "Bundle.entry.resource.ofType(Patient)" ); *
Patient                         patient    = ( Patient ) patients.get( 0 );

Extra credit

Once you've got a Patient, such as produced above, you can isolate a particular Identifier (for the reason of this, see Yet More FHIRPath...).


...

// extracting a specific Identifier resource from the Patient using FHIRPath:
List< Base > identifiers = fhirEngine.evaluate( patient, "Patient.identifier.where(extension('http://acme.us/fhir/extensions/mpi'))" );
Identifier   identifier  = identifiers.get( 0 );

The "cost" of FHIRPath

Previously, you learned that this code will get you a patient out of a bundle:

FhirContext                     context    = FhirContext.forR5();
IParser                         parser     = context.newXmlParser();
BaseResource                    bundle     = parser.parseResource( CONTENT.getBytes() );
DefaultProfileValidationSupport validation = new DefaultProfileValidationSupport( context );
IWorkerContext                  worker     = new HapiWorkerContext( context, validation );
FHIRPathEngine                  engine     = new FHIRPathEngine( worker );
List< Base >                    patients   = fhirEngine.evaluate( bundle, "Bundle.entry.resource.ofType(Patient)" ); *
Patient                         patient    = ( Patient ) patients.get( 0 );

However, you must understand the cost to execute. The highlighted statements here cost, in my present development environment, 2 seconds or a bit more, while the elapsed time to execute the other statements is near 0.

Thus, you'll want to isolate your initialzation of the FHIRPath engine to a run-once or initialization segment of your application:

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import org.hl7.fhir.r5.hapi.ctx.HapiWorkerContext;
import org.hl7.fhir.r5.utils.FHIRPathEngine;
.
.
.
final FhirContext                     context    = FhirContext.forR5();
final DefaultProfileValidationSupport validation = new DefaultProfileValidationSupport( context );
final IWorkerContext                  worker     = new HapiWorkerContext( context, validation );
final FHIRPathEngine                  engine     = new FHIRPathEngine( worker );

More FHIRPath experimentation...

Experimentation: I do it the quick way, which is tantamount to writing HAPI FHIR code as if I know what I'm doing (well, okay, I do) then I resort to using FHIRPath three times, three different ways to achieve the same result. FHIRPath is going to be useful in the case where I "don't know what I'm doing," for example, the path is passed into me to look up in a resource (so I can't code sciently ahead).

The lines implementing a "direct," i.e.: sans FHIRPath, approach are highlighted here. The remaining lines employ the IFhirPath.evaluate() solution.

Imports to add to what's already being used are listed here.

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import org.hl7.fhir.r4.model.Medication;
import org.hl7.fhir.r4.model.MedicationDispense;
import org.hl7.fhir.r4.model.Resource;

  @Test
  public void testMedicationEmbeddedInMedicationDispense() throws IOException
  {
    final String              PATHNAME           = TestUtilities.TEST_RESOURCES + "flows/test-files/medicationdispense-3.xml";
    final String              CONTENT            = TestUtilities.getLinesInFile( PATHNAME );
    final FhirContext         context            = FhirContext.forR4();
    final IParser             parser             = context.newXmlParser();
    final MedicationDispense  medicationDispense = parser.parseResource( MedicationDispense.class,
                                                        new ByteArrayInputStream( CONTENT.getBytes() ) );
    List< Resource > resources  = medicationDispense.getContained(); assertFalse( resources.isEmpty() );
    Resource         resource   = resources.get( 0 );                assertTrue( resource instanceof Medication );
    Medication       medication = ( Medication ) resource;
    System.out.println( medication.fhirType() );

    final String           medicationPath  = "MedicationDispense.contained";
    Optional< Medication > m               = context.newFhirPath().evaluateFirst( medicationDispense, medicationPath, Medication.class );
    System.out.println( m.get().fhirType() );

    Optional< Resource >   r               = context.newFhirPath().evaluateFirst( medicationDispense, medicationPath, Resource.class );
    System.out.println( r.get().fhirType() );

    final String           medicationPath2 = "MedicationDispense.contained.ofType(Medication)";
    Optional< Medication > m2              = context.newFhirPath().evaluateFirst( medicationDispense, medicationPath2, Medication.class );
    System.out.println( m2.get().fhirType() );

    /* output
        Medication
        Medication
        Medication
        Medication
     */
  }

Yet more FHIRPath...

FHIRPath is pretty powerful. You can do all sorts of crazy things (if you don't mind waiting—see notes on cycles consumed above).

Here's a way to pluck a value out of an identifier that's concocted (proprietarily) to hold something called a "master patient index" or MPID. (An example of this in XML is given at the end of this note.)

package com.windofkeltia.hapi.fhir;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.List;

import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.parser.IParser;
import org.hl7.fhir.r5.hapi.ctx.HapiWorkerContext;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.BaseResource;
import org.hl7.fhir.r5.model.Encounter;
import org.hl7.fhir.r5.model.Extension;
import org.hl7.fhir.r5.model.Identifier;
import org.hl7.fhir.r5.model.Patient;
import org.hl7.fhir.r5.utils.FHIRPathEngine;

import com.windofkeltia.utilities.TestUtilities;

public class ExploitFhirPathTest
{
  @Rule   public TestName name = new TestName();
  @After  public void tearDown() { }
  @Before public void setUp()
  {
    TestUtilities.setUp( name );
    context    = FhirContext.forR5();
    parser     = context.newXmlParser().setPrettyPrint( true );
    fhirEngine = new FHIRPathEngine( new HapiWorkerContext( context, new DefaultProfileValidationSupport( context ) ) );
  }

  private static final boolean VERBOSE  = TestUtilities.verboseIfAttentive();

  private FhirContext    context;
  private IParser        parser;
  private FHIRPathEngine fhirEngine;

  @Test
  public void test() throws IOException
  {
    final int    LIMIT    = 100000;
    final String FHIRPATH = "Patient.identifier.where(extension('http://acme.us/fhir/extensions/mpi')).value";
    long         usingFhirPath, goingDirect;

    Patient patient = getLilyMunster();

    // measure FHIRPath elapsed time ------------------------------------------------------------------------------
    usingFhirPath = System.currentTimeMillis();

    for( int count = 0; count < LIMIT; count++ )
    {
      List< Base > values = fhirEngine.evaluate( patient, FHIRPATH );

      if( values.size() != 1 )
        fail( "No MPID found" );

      String mpid = String.valueOf( values.get( 0 ) );
    }

    usingFhirPath = System.currentTimeMillis() - usingFhirPath;

    // measure direct-code elapsed time ---------------------------------------------------------------------------
    goingDirect = System.currentTimeMillis();

    for( int count = 0; count < LIMIT; count++ )
    {
      List< Identifier > identifiers = patient.getIdentifier();

      Identifier mpidIdentifier = null;

      for( Identifier identifier : identifiers )
      {
        List< Extension > extensions = identifier.getExtension();

        for( Extension extension : extensions )
        {
          String url = extension.getUrl();

          if( url.equals( "http://acme.us/fhir/extensions/mpi" ) )
          {
            mpidIdentifier = identifier;
            break;
          }
        }

        if( nonNull( mpidIdentifier ) )
          break;
      }

      if( isNull( mpidIdentifier ) )
        fail( "Unable to find desired extension URL" );

      String mpid = mpidIdentifier.getValue();

      if( mpid.isEmpty() )
        fail( "No MPID found" );
    }

    goingDirect = System.currentTimeMillis() - goingDirect;

    if( VERBOSE )
    {
      System.out.println( "Benchmark, executing " + LIMIT + " iterations," );
      System.out.println( "         FHIRPath took " + usingFhirPath + " milliseconds" );
      System.out.print  ( "  direct approach took " + goingDirect + " millisecond" );
      System.out.println( ( goingDirect == 1 ) ? "" : "s" );
    }
  }

  /** "Lily Munster" because that's the Patient's name in our test fodder. */
  private Patient getLilyMunster() throws IOException
  {
    final String PATHNAME = TestUtilities.TEST_FODDER + "/lily-munster.xml";
    final String CONTENT  = TestUtilities.getLinesInFile( PATHNAME );
    BaseResource resource = ( BaseResource ) parser.parseResource( new ByteArrayInputStream( CONTENT.getBytes() ) );
    List< Base > patients = fhirEngine.evaluate( resource, "Bundle.entry.resource.ofType(Patient)" );

    if( patients.isEmpty() )
      fail( "Failed to find Patient Lily Munster" );

    return ( Patient ) patients.get( 0 );
  }
}

I got the following output at one point:

Test: testBenchmark ---------------------------------------------------------------------------------------
Benchmark, executing 100000 iterations,
         FHIRPath took 537 milliseconds
  direct approach took 7 milliseconds

As you can see, using FHIRPath, once you've set it up, is elegant, but there's certainly a substantial penalty.

Here's lily-munster.xml and the identifier that interests us in particular:

<Bundle xmlns="http://hl7.org/fhir">
  <entry>
    <resource>
      <Patient xmlns="http://hl7.org/fhir">
        <meta>
          <lastUpdated value="2021-01-05T00:00:00.000-06:00" />
        </meta>
        <id value="9812850.PI"/>
        <identifier>
          <extension url="http://acme.us/fhir/extensions/mpi">
            <extension url="PIRecord">
              <valueString value="9812850.PI"/>
            </extension>
          </extension>
          <system value="https://test.acme.io/mpi/0/" />
          <value value="665892"/>
          <assigner>
            <display value="Test Performance MPI system"/>
          </assigner>
        </identifier>
        <identifier>
          <system value="https://fhir.acme.io/facility/Beverly Hills Clinic" />
          <value value="3660665800" />
          <assigner>
            <display value="Beverly Hills Clinic" />
          </assigner>
        </identifier>
        <name>
          <family value="Munster" />
          <given value="Lily" />
        </name>
        <gender value="female" />
        <birthDate value="1827-04-01" />
        <deceasedBoolean value="false" />
        <address>
          <text value="1313 Mockingbird Lane, Mockingbird Heights, Beverly Hills, CA 90210" />
          <line value="1313 Mockingbird Lane" />
          <city value="Beverly Hills" />
          <state value="CA"/>
          <postalCode value="90210" />
        </address>
        <telecom>
          <system value="phone" />
          <value value="+3035551212" />
          <use value="home" />
        </telecom>
      </Patient>
    </resource>
  </entry>
  <entry>
    <fullUrl value="urn:uuid:f78d73fc-9f9b-46d5-93aa-f5db86ba914c" />
    <resource>
      <Encounter xmlns="http://hl7.org/fhir">
        <class>
          <system value="http://terminology.hl7.org/CodeSystem/v3-ActCode" />
          <code value="EMER" />
        </class>
        <type>
          <coding>
            <system value="http://snomed.info/sct" />
            <code value="410429000" />
            <display value="Cardiac Arrest" />
          </coding>
          <text value="Cardiac Arrest" />
        </type>
        <subject>
          <reference value="urn:uuid:5cbc121b-cd71-4428-b8b7-31e53eba8184" />
          <display value="Mrs. Lily Munster" />
        </subject>
        <participant>
          <individual>
            <reference value="urn:uuid:0000016d-3a85-4cca-0000-000000000122" />
            <display value="Victor von Frankenstein, M.D." />
          </individual>
        </participant>
        <period>
          <start value="1965-11-15T06:22:41-05:00" />
          <end value="1965-11-15T08:07:41-05:00" />
        </period>
        <serviceProvider>
          <reference value="urn:uuid:8ad64ecf-c817-3753-bee7-006a8e662e06" />
          <display value="Beverly Hills Hospital" />
        </serviceProvider>
      </Encounter>
    </resource>
    <request>
      <method value="POST" />
      <url value="Encounter" />
    </request>
  </entry>
</Bundle>

Still more FHIRPath illustrations: unclear datum types...

In an HL7v4 Observation and many other FHIR resources, there is a field, status whose type is often Observation.ObservationStatus, Claim.ClaimStatus, etc.

In most cases, when using a FHIRPath to reach a datum, you want to have to express the least amount of knowledge of the object (Observation, Account, etc.) possible. Yet, how to arrive at a datum documented thus? (channeling HL7FHIR Observation: Resource Content here)

status    1..1 code    registered | preliminary | final | amended +

You're tempted to give into this:

FhirContext context = FhirContext.forR4();
IParser     parser  = context.newXmlParser().setPrettyPrint( true );
Claim       claim   = parser.parseResource( Claim.class, "<Claim ..." );

if( !claim.hasStatus() )
  fail( "Test fodder needs a Claim." );

Object object = claim.getStatus();

if( object instanceof Claim.ClaimStatus )
{
  String status = ( ( Claim.ClaimStatus ) object ).getDisplay();
  System.out.println( "Status: " + status );
}

...but that would mean having knowledge about the object when you just have a FHIRPath and don't want to have to know that the datum at the end of the path is a Claim.ClaimStatus.

The generic solution for handling this case is as follows. Note that the only appearance of "Observation" is in the FHIRPath, not in the Java code:

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.parser.IParser;

import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext;
import org.hl7.fhir.r4.model.Base;
import org.hl7.fhir.r4.model.Enumeration;
import org.hl7.fhir.r4.utils.FHIRPathEngine;

@Test
public void test()
{
  final String FHIRPATH = "Bundle.entry.resource.ofType(Observation).status";

  FhirContext                     context     = FhirContext.forR4();
  IParser                         parser      = context.newXmlParser().setPrettyPrint( true );
  DefaultProfileValidationSupport validation  = new DefaultProfileValidationSupport( context );
  HapiWorkerContext               worker      = new HapiWorkerContext( context, validation );
  FHIRPathEngine                  engine      = new FHIRPathEngine( worker );
  Bundle                          bundle      = parser.parseResource( Bundle.class, BUNDLE );

  List< Base >                    values      = engine.evaluate( observation, FHIRPATH );

  if( values.size() != 1 )
    fail( "Unable to find " + FHIRPATH );

  Object object = values.get( 0 );

  System.out.println( "Object: " + object );

  if( object instanceof Enumeration< ? > )
    System.out.println( "Object is Enumeration< ? >" );

  System.out.println( "String: " + String.valueOf( object ) );

  Enumeration< ? > enumeration = ( Enumeration< ? > ) object;
  String           e           = enumeration.getValueAsString();

  System.out.println( "Enumeration string: " + e );
}

Output from test():

Object: Enumeration[final]
Object is Enumeration< ? >
String: Enumeration[final]
Enumeration string: final

The solution in the case of status, which is a code, is to exploit it as a generic Enumeration (of unknown type). I have left in verbose stabbing around in the code, which you can observe in the debugger, to show how to arrive at this conclusion because it's a principle that maps to other, similar type-difficult situations arising at the end of a FHIRPath.


FHIRPath end-point catch-all

Still, about 50% of datatypes or more at the end of any FHIRPath* can be resolved to strings thus:

  final String FHIRPATH = "Bundle.entry.resource.ofType(Observation).valueInteger";
  ...
  List< Base > values = engine.evaluate( observation, FHIRPATH );

  if( values.size() != 1 )
    fail( "Unable to find " + FHIRPATH );

  Object object = values.get( 0 );

  try
  {
    String value = values.get( 0 );
    System.out.println( "Significant value: " + value );
    return value;
  }
  catch( NullPointerException e )
  {
    if( nonNull( logger ) )
      logger.debug( "Extractor unable to harvest string from data type pointed at by the end of a FHIRPath" );
    return null;
  }
}

* Of course, we're talking about really generic FHIRPaths and not the very exact ones. Across the breadth of this topic, with my advice, your mileage may seriously vary.


FHIRPath examples

Extract the references from the first subject of an Observation:

Bundle.entry.resource.ofType(Observation).subject[0].reference.value

[Output] Patient/ebb16c62-cb06-4ff8-8ce8-ccb865e7a240

Extract the references from the first subject alternative:

Bundle.entry.resource.ofType(Observation).subject.first().reference.value

[Output] Patient/ebb16c62-cb06-4ff8-8ce8-ccb865e7a240

Select the first Patient reference where the diastolic is over 90:

Bundle.entry.resource.ofType(Observation).where(component.where(code.coding.code = '8462-4').value.where( value > 90.0).exists()).subject.reference.value.first()

[Output] Patient/5fabfccd-254e-42af-bed2-84199a5c05f2

Select the Patient's first name where the reference matches the logical id:

Bundle.entry.resource.ofType(Patient).where(id = '" + v.split("/")[1] + "').name.given.value

[Output] John

FHIRPath functions

"Functions" are syntactically expressed facilities that appear in the FHIRPath and aid in reaching a particular datum wanted.

Here is a summary to tease the imagination, but you can find the definitive treatment here: FHIRPath.

  1. extension(url : string) : collection

    Filter the input collection for items named "extension" with the given url. This is a syntax shortcut for .extension.where(url = string), but is simpler to write. Will return an empty collection if the input collection is empty or the url is empty.

  2. hasValue() : Boolean

    Returns true if the input collection contains a single value which is a FHIR primitive.

  3. getValue() : System.[type]

    Return the underlying system value for the FHIR primitive if the input collection contains a single value which is a FHIR primitive, and it has a primitive value (see discussion for hasValue()). Otherwise the return value is empty.

  4. resolve() : collection

    For each item in the collection, if it is a string that is a uri (or canonical or url), locate the target of the reference, and add it to the resulting collection.

  5. ofType(type : identifier) : collection

    An alias for ofType() maintained purely for backwards compatibility.

  6. ofType(type : identifier) : collection

    Returns a collection that contains all items in the input collection that are of the given type or a subclass thereof.

  7. elementDefinition() : collection

    Returns the FHIR element definition information for each element in the input collection.

  8. slice(structure : string, name : string) : collection

    Returns the given slice as defined in the given structure definition. The structure argument is a uri.

  9. checkModifiers(modifier : string) : collection

    For each element in the input collection, verifies that there are no modifying extensions defined other than the ones given by the modifier argument (comma-separated string).

  10. conformsTo(structure : string) : Boolean

    Returns true if the single input element conforms to the profile specified by the structure argument, and false otherwise.

  11. memberOf(valueset : string) : Boolean

    When invoked on a single code-valued element, returns true if the code is a member of the given valueset.

  12. subsumes(code : Coding | CodeableConcept) : Boolean

    When invoked on a Coding-valued element and the given code is Coding-valued, returns true if the source code is equivalent to the given code, or if the source code subsumes the given code.

  13. subsumedBy(code: Coding | CodeableConcept) : Boolean

    When invoked on a Coding-valued element and the given code is Coding-valued, returns true if the source code is equivalent to the given code.

  14. htmlChecks : Boolean

    When invoked on a single xhtml element returns true if the rules around HTML usage are met, and false if they are not.

  15. lowBoundary : T

    This function returns the lowest possible value in the natural range expressed by the type it is invoked on.

  16. highBoundary : T

    This function returns the lowest possible value in the natural range expressed by the type it is invoked on.

  17. comparable(quantity) : boolean

    This function returns true if the engine executing the FHIRPath statement can compare the singleton Quantity with the singleton other Quantity and determine their relationship to each other.


How to find HAPI FHIR Javadoc...

This is a "how-to" here...

This is nasty. If you google "hapi fhir javadoc" or even "hapi fhir humanname javadoc" you end up with a list of links the best of which lands you only here:

https://hapifhir.io/hapi-fhir/docs/appendix/javadocs.html

From there, it's frustrating. What you need to understand is tricks to get you over the next couple of hyperlink traversals and into the meat. Probably, because what you're looking for is data structures that help you anaylzed what is parsed, think "model":

https://hapifhir.io/hapi-fhir/apidocs/hapi-fhir-structures-r4/

Then (keep thinking "model"):

https://hapifhir.io/hapi-fhir/apidocs/hapi-fhir-structures-r4/org/hl7/fhir/r4/model/package-summary.html

and, finally, you can start looking seriously, e.g.: (in Chrome) Ctrl-F, type "humanname":

https://hapifhir.io/hapi-fhir/apidocs/hapi-fhir-structures-r4/org/hl7/fhir/r4/model/HumanName.html

Then type "period" (if you're looking to figure out HAPI FHIR's Period type (start and end dates) as consumed for a name, an address or a telecom in the FHIR Patient record.

I find this infuriatingly "thick" given how easy other frameworks make it (although, in fairness, for years to look at NiFi Javadoc, you pretty well had to pull down the source code--we only use the binary downloads--and build it yourself).


On the FHIR parser...

When you run the HAPI FHIR parser, if you don't know the type of resource that's in the document and you don't care, just use IBaseResource. If we did care, we might substitute Bundle or Patient or MedicationRequest, etc. in place of IBaseResource.

Note that conveniently there are three versions of IParser.parseResource() for FHIR as a String, FHIR on an input stream and FHIR in a reader:

  1. IBaseResource parseResource( String string ) —used here
  2. IBaseResource parseResource( InputStream inputStream )
  3. IBaseResource parseResource( Reader reader )

...and, almost as conveniently, two versions of IParser.encodeResourceXyz():

  1. String encodeResourceToString( IBaseResource resource )
  2. void encodeResourceToWriter( IBaseResource resource, Writer writer )

Best practice for determining if incoming document is XML or JSON...
String document; // the FHIR content

switch( EncodingEnum.detectEncoding( document ) )
{
  case XML :  break;
  case JSON : break;
  case RDF :  break;
}

Best practice for determining what FHIR resource dominates the document...

Assume that we know the document is in XML.

String document; // the FHIR content

IParser       parser   = context.newXmlParser();
IBaseResource resource = parser.parseResource( document );

What happens if there's junk in the FHIR?

By junk, I mean extra elements under a resource that are not FHIR, that is, they are not documented at Resource Index or below as linked.

Now, the FHIR validator will log them as bad. However, HAPI FHIR is a serious tool for getting work done. If it blew chunks for every tiny mistake, it's easy to see that it would be very hard to write a parsed analysis of an incoming document.

Enhancing ugly XML formatting like <element>(no text here)</element> instead of <element />

Someone in the HAPI FHIR group mentioned that he had problems with ugly XML generation in HAPI FHIR whenever the STaX library was missing. I need to check into that. It turns out that adding this to pom.xml is the solution:

<dependency>
  <groupId>org.codehaus.woodstox</groupId>
  <artifactId>woodstox-core-asl</artifactId>
  <version>4.2.0</version>
</dependency>

Generating Identifier constructs...

Here we are brute-force generating an identifier such as might go underneath a Patient:

public static void injectIdentifierThingIntoPatient( Patient patient )
{
  // create <type><coding>...
  CodeableConcept codeableConcept = new CodeableConcept();
  Coding          coding          = new Coding();
  coding.setSystem( "https://fhir.acme.io/fhir/code/thing" );
  coding.setCode( "thing" );
  coding.setDisplay( "Thing stuff..." );

  // and the array to keep them in...
  List< Coding > codings = new ArrayList<>();
  codings.add( coding );
  codeableConcept.setCoding( codings );

  // create the <assigner>...
  Reference assigner = new Reference();
  assigner.setDisplay( "Patient thing" );

  // create the top-level <identifier> and link the guts in...
  Identifier thing = new Identifier();
  thing.setUse( Identifier.IdentifierUse.USUAL );
  thing.setType( codeableConcept );
  thing.setSystem( "https://debug.acme.io/thing/0" );
  thing.setValue( "99" );
  thing.setAssigner( assigner );

  // now link it into the patient our caller gave us...
  List< Identifier > identifiers = patient.getIdentifier();
  identifiers.add( thing );
  patient.setIdentifier( identifiers );
}

Generated, it looks something like this:

<identifier>
  <use value="usual" />
  <type>
    <coding>
      <system value="http://fhir.acme.us/fhir/codes/thing" />
      <code value="thing" />
      <display value="Thing stuff..." />
    </coding>
  </type>
  <system value="https://debug.acme.io/mpi/0/" />
  <value value="99" />
  <assigner>
    <display value="Patient thing"/>
  </assigner>
</identifier>

If this happens frequently enough, it could be usefully rolled into a static utility class using a Builder Pattern. The Builder interface would be something like:

import java.util.ArrayList;
import java.util.List;

import static java.util.Objects.nonNull;

import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Identifier;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Reference;

public class FhirIdentifier
{
  private final String codeableCode;
  private final String codeableSystem;
  private final String codeableDisplay;
  private final String assignerDisplay;
  private final String system;
  private final String value;
  private final Identifier.IdentifierUse use;

  protected FhirIdentifier( Builder builder )
  {
    codeableCode    = builder.codeableCode;
    codeableSystem  = builder.codeableSystem;
    codeableDisplay = builder.codeableDisplay;
    assignerDisplay = builder.assignerDisplay;
    system          = builder.system;
    value           = builder.value;
    use             = builder.use;
  }

  public void injectInto( Patient patient )  // maybe soften this if Identifiers can go into other FHIR resources
  {
    // create <type><coding>...
    CodeableConcept codeableConcept = new CodeableConcept();
    Coding          coding          = new Coding();
    if( nonNull( codeableCode ) )
      coding.setCode( codeableCode );
    if( nonNull( codeableSystem ) )
      coding.setSystem( codeableSystem );
    if( nonNull( codeableDisplay ) )
      coding.setDisplay( codeableDisplay );

    // and the array to keep them in...
    List< Coding > codings = new ArrayList<>();
    codings.add( coding );
    codeableConcept.setCoding( codings );

    // create the top-level <identifier> and link the guts in...
    Identifier thing = new Identifier();
    if( nonNull( use ) )
      thing.setUse( use );
    thing.setType( codeableConcept );
    if( nonNull( system ) )
      thing.setSystem( system );
    if( nonNull( value ) )
      thing.setValue( value );

    // create the <assigner>...
    if( nonNull( assignerDisplay ) )
    {
      Reference assigner = new Reference();
      assigner.setDisplay( assignerDisplay );
      thing.setAssigner( assigner );
    }

    // now link it into the patient our caller gave us...
    List< Identifier > identifiers = patient.getIdentifier();
    identifiers.add( thing );
    patient.setIdentifier( identifiers );
  }

  private static Identifier.IdentifierUse getUse( final String use )
  {
    return Identifier.IdentifierUse.valueOf( use );
  }

  public static class Builder
  {
    private String codeableCode;
    private String codeableSystem;
    private String codeableDisplay;
    private String assignerDisplay;
    private String system;
    private String value;
    private Identifier.IdentifierUse use;

    public Builder codeableCode( String code )       { this.codeableCode    = code;    return this; }
    public Builder codeableSystem( String system )   { this.codeableSystem  = system;  return this; }
    public Builder codeableDisplay( String display ) { this.codeableDisplay = display; return this; }
    public Builder assignerDisplay( String display ) { this.assignerDisplay = display; return this; }
    public Builder system( String system )           { this.system          = system;  return this; }
    public Builder value( String value )             { this.value           = value;   return this; }
    public Builder use( String use )                 { this.use             = getUse( use ); return this; }
    public FhirIdentifier build()                    { return new FhirIdentifier( this ); }
  }
}

...and used like this:

FhirIdentifier identifier = new FhirIdentifier.Builder()
                                   .codeableCode( "thing" )
                                   .codeableSystem( "http://fhir.acme.us/fhir/codes/thing" )
                                   .codeableDisplay( "Thing stuff..." )
                                   .system( "https://debug.acme.io/mpi/0/" )
                                   .value( "99" )
                                   .assignerDisplay( "Patient thing" )
                                   .use( "usual" )
                                   .build();
identifier.injectInto( patient );

Patients, Identifiers and Extensions

Parsing this:


  
  
    
      
        
      
    
    
    
    
      
    
  

...we do this:

import org.hl7.fhir.r4.model.Extension;
import org.hl7.fhir.r4.model.Identifier;
import org.hl7.fhir.r4.model.Patient;
.
.
.
public class FunWithPatientIdentifiersAndExtensions
{
  public void analyzeIdentifierWithExtensions( Patient patient )
  {
    for( Identifier identifier : patient.getIdentifier() )
    {
      Extension extension = identifier.getExtensionByUrl( "http://fhir.acme.us/fhir/extensions/mpi" );

      if( isNull( extension ) )
        break;

      MPID = identifier.getValue();

      Extension piExtension = extension.getExtensionByUrl( "PIRecord" );
      PI = String.valueOf( piExtension.getValue() ); // (reason for this example)
      break;
    }
  }

CodeableConcepts and Extensions

Creating what's in bold here:

<identifier>
  <type>
    <coding>
        <extension url="http://acme.us/fhir/codes/">
          <extension url="systemdisplay">
            <valueString value="SNOMED" />
          </extension>
        </extension>
        <system value="http://snomed.info/sct" />
        <code value="38341003" />
        <display value="Hypertension" />
    </coding>
    <text value="Hypertension" />
  </type>
</identifier>
for( Coding coding : codings )
{
  if( coding.getSystem().equals( "http://snomed.info/sct" ) )
  {
    // add an extesion to help human-readability...
    Extension subExtension = new Extension();
    subExtension.setUrl( "systemdisplay" );
    subExtension.setValue( new StringType( "SNOMED" ) );
    Extension extension = new Extension();
    extension.setUrl( "http://windofkeltia.com/fhir/codes/" );
    extension.addExtension( subExtension );
    coding.addExtension( extension );
  }
}

Updating HAPI FHIR versions

If you wish to update HAPI FHIR, e.g.: from 5.1.0 to 5.2.0, you will almost surely get this message when you run (JUnit tests, etc.):


java.lang.IllegalStateException: Could not find the HAPI-FHIR structure JAR on the classpath for version R5.
    Note that as of HAPI-FHIR v0.8, a separate FHIR strcture JAR must be added to your classpath
    (or project pom.xml if you are using Maven)

and you'll have to:

  1. Ensure that every vestige of 5.1.0 get eradicated (from pom.xml, any direct references to JARs, etc.).
  2. Bounce IntelliJ IDEA by File → Invalidate Caches/Restart.

HAPI FHIR FHIRPath

Was working on FHIRPath a couple of weeks ago. I have decided to put this work here. There is precious little documentation on how to use FHIRPath on the HAPI FHIR website or even Googling "x"fhirpath examples" and "fhirpathengine examples" which is a fruitless exercise.

This is a simple way to get started. James Agnew calls this "agnostic FHIRPath" as compared to using HapiWorkerContext (as I do later on).

FhirContext context = FhirContext.forR5();
IFhirPath   path    = context.newFhirPath();
package com.windofkeltia;

import java.util.List;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.fhirpath.IFhirPath;
import ca.uhn.fhir.parser.IParser;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r5.hapi.ctx.HapiWorkerContext;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.Bundle;
import org.hl7.fhir.r5.model.Patient;
import org.hl7.fhir.r5.model.StringType;
import org.hl7.fhir.r5.utils.FHIRPathEngine;

import utilities.TestUtilities;

/**
 * @author Russell Bateman
 * @since January 2021
 */
public class FhirPathTest
{
  @Rule   public TestName name = new TestName();
  @After  public void tearDown() { }
  @Before public void setUp()    { TestUtilities.setUp( name ); }

  private static final boolean VERBOSE = TestUtilities.VERBOSE;

  private static final String FHIR_CONTENT = ""
      + "<Bundle xmlns=\"http://hl7.org/fhir\">\n"
      + "  <type value=\"transaction\"/>\n"
      + "  <entry>\n"
      + "    <fullUrl value=\"urn:uuid:38826a3f-8e5c-4846-95c3-7f12a233447d\"/>\n"
      + "    <resource>\n"
      + "      <Patient xmlns=\"http://hl7.org/fhir\">\n"
      + "        <meta>\n"
      + "          <lastUpdated value=\"2021-01-05T00:00:00.000-06:00\" />\n"
      + "        </meta>\n"
      + "        <identifier>\n"
      + "          <system value=\"https://fhir.acme.io/facility/Beverly Hills Clinic\" />\n"
      + "          <value value=\"3660665800\" />\n"
      + "          <assigner>\n"
      + "            <display value=\"Beverly Hills Clinic\" />\n"
      + "          </assigner>\n"
      + "        </identifier>\n"
      + "        <name>\n"
      + "          <family value=\"Munster\" />\n"
      + "          <given value=\"Herman\" />\n"
      + "        </name>\n"
      + "        <gender value=\"male\" />\n"
      + "        <birthDate value=\"1835-10-31\" />\n"
      + "        <deceasedBoolean value=\"false\" />\n"
      + "        <address>\n"
      + "          <text value=\"1313 Mockingbird Lane, Mockingbird Heights, CA 90210\" />\n"
      + "          <line value=\"1313 Mockingbird Lane\" />\n"
      + "          <city value=\"Mockingbird Heights\" />\n"
      + "          <state value=\"CA\"/>\n"
      + "          <postalCode value=\"90210\" />\n"
      + "        </address>\n"
      + "        <telecom>\n"
      + "          <system value=\"phone\" />\n"
      + "          <value value=\"+3035551212\" />\n"
      + "          <use value=\"home\" />\n"
      + "        </telecom>\n"
      + "      </Patient>\n"
      + "    </resource>\n"
      + "  </entry>\n"
      + "  <entry>\n"
      + "    <resource>\n"
      + "      <Encounter xmlns=\"http://hl7.org/fhir\">\n"
      + "        <id value=\"emerg\" />\n"
      + "        <period>\n"
      + "          <start value=\"2017-02-01T08:45:00+10:00\" />\n"
      + "          <end value=\"2017-02-01T09:27:00+10:00\" />\n"
      + "        </period>\n"
      + "      </Encounter>\n"
      + "    </resource>\n"
      + "    <request>\n"
      + "      <method value=\"POST\" />\n"
      + "      <url value=\"Patient\" />\n"
      + "    </request>\n"
      + "  </entry>\n"
      + "</Bundle>\n";

  @Test
  public void testBundle()
  {
    FhirContext context = FhirContext.forR5();
    IParser     parser  = context.newXmlParser();
    IFhirPath   where   = context.newFhirPath();
    Bundle      bundle  = ( Bundle ) parser.parseResource( FHIR_CONTENT );

    List< IBaseResource > resources = where.evaluate( bundle, "Bundle.entry.resource",  IBaseResource.class );
    System.out.println( resources );
  }

  /* output:
  [org.hl7.fhir.r5.model.Patient@5ea502e0, org.hl7.fhir.r5.model.Encounter@443dbe42]
   */

  @Test
  public void testPatient()
  {
    FhirContext context  = FhirContext.forR5();
    IParser     parser   = context.newXmlParser();
    IFhirPath   where    = context.newFhirPath();
    Bundle      bundle   = ( Bundle ) parser.parseResource( FHIR_CONTENT );

    for( Bundle.BundleEntryComponent component : bundle.getEntry() )
    {
      IBaseResource resource = component.getResource();

      if( !( resource instanceof Patient ) )
        continue;

      Patient            patient     = ( Patient ) resource;
      List< StringType > familyNames = where.evaluate( patient, "Patient.name.family", StringType.class );
      System.out.println( familyNames.get( 0 ) );
    }
  }

  /* output:
  Munster
   */

  @Test
  public void testFun()
  {
    FhirContext    context        = FhirContext.forR5();
    IParser        parser         = context.newXmlParser();
    Bundle         bundle         = parser.parseResource( Bundle.class, FHIR_CONTENT );
    FHIRPathEngine fhirPathEngine = new FHIRPathEngine( new HapiWorkerContext( context, new DefaultProfileValidationSupport( context ) ) );
    List< Base >   familyNames    = fhirPathEngine.evaluate( bundle.getEntry().get( 0 ).getResource(), "Patient.name.family" );
    List< Base >   dateTypes      = fhirPathEngine.evaluate( bundle.getEntry().get( 0 ).getResource(), "Patient.birthDate" );
    System.out.println( familyNames.get( 0 ).toString() );
    System.out.println( dateTypes.get( 0 ).dateTimeValue().getValueAsString() );
  }

  /* output:
  Munster
  1835-10-31
   */

  @Test
  public void testFamilyName()
  {
    FhirContext    context        = FhirContext.forR5();
    IParser        parser         = context.newXmlParser();
    Bundle         bundle         = parser.parseResource( Bundle.class, FHIR_CONTENT );
    FHIRPathEngine fhirPathEngine = new FHIRPathEngine( new HapiWorkerContext( context, new DefaultProfileValidationSupport( context ) ) );
    List< Base >   patients       = fhirPathEngine.evaluate( bundle.getEntry().get( 0 ).getResource(), "Patient" );
    Patient        patient        = ( Patient ) patients.get( 0 );
    List< Base >   familyNames    = fhirPathEngine.evaluate( patient, "Patient.name.family" );
    System.out.println( familyNames.get( 0 ).toString() );

    String familyName;
    familyName = fhirPathEngine.evaluateToString( bundle.getEntry().get( 0 ).getResource(), "Patient.name.family" );
    System.out.println( familyName );
    familyName = fhirPathEngine.evaluateToString( patient, "Patient.name.family" );
    System.out.println( familyName );
    int y = 9;
  }

  /* output:
  Munster
  Munster
  Munster
   */

  @Test
  public void testBirthdate()
  {
    FhirContext    context        = FhirContext.forR5();
    IParser        parser         = context.newXmlParser();
    Bundle         bundle         = parser.parseResource( Bundle.class, FHIR_CONTENT );
    FHIRPathEngine fhirPathEngine = new FHIRPathEngine( new HapiWorkerContext( context, new DefaultProfileValidationSupport( context ) ) );
    List< Base >   patients       = fhirPathEngine.evaluate( bundle.getEntry().get( 0 ).getResource(), "Patient" );
    Patient        patient        = ( Patient ) patients.get( 0 );

    List< Base >   birthDates     = fhirPathEngine.evaluate( patient, "Patient.birthDate" );
    System.out.println( birthDates.get( 0 ).dateTimeValue().getValueAsString() );
  }

  /* output:
  1835-10-31
   */
}

The FHIR validator

The official FHIR validator is a useful JAR whose usage is explained at this hyperlink. The JAR is available at this link. All the details on use, and they are numerous and confusing, are available from that page. The validator JAR can be easily made accessible from the command line thus:

#!/bin/sh
if [ "$1" = "--help" ]; then
  echo "Usage: $0  [-output ]"
	exit 0
fi
VALIDATOR_PATH="/home/russ/dev/fhir-validator/validator_cli.jar"
java -jar $VALIDATOR_PATH -version 4.0 $*

Use is this way:

~/bin $ ./fhir-validator.sh --help
Usage: ./fhir-validator.sh  [-output ]
~/bin $ fhir-validator.sh FHIR-document validator-output
FHIR Validation tool Version 5.1.17 (Git# 44f7dca1c794). Built 2020-10-14T20:04:32.293Z (11 days old)
  Java:   1.8.0_265 from /usr/lib/jvm/java-8-openjdk-amd64/jre on amd64 (64bit). 3538MB available
  Paths:  Current = /home/russ/bin, Package Cache = /home/russ/.fhir/packages
  Params: -version 4.0 mds-extract
Loading
  Load FHIR v4.0 from hl7.fhir.r4.core#4.0.1 - 4575 resources (00:04.0220)
  Terminology server http://tx.fhir.org - Version 1.0.362 (00:01.0186)
  Get set...  go (00:00.0003)
Validating
  Validate mds-extract   ..Detect format for mds-extract
.
.
.

Some beginning steps...

Here are the Maven dependencies for the code about to come. (This isn't the whole pom.xml.) This was the latest version of HAPI-FHIR at the time I wrote this. In particular, I'm guided by HAPI-FHIR Documentation, specifically, the link Parsing and Serializing. All this HAPI-FHIR software was written by or under the ægis of James Agnew—huge kudos to him.

pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                      http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <properties>
    <hapi-fhir.version>4.2.0</hapi-fhir.version>
  </properties>

  <dependency>
    <groupId>ca.uhn.hapi.fhir</groupId>
    <artifactId>hapi-fhir-base</artifactId>
    <version>${hapi-fhir.version}</version>
  </dependency>
  <dependency>
    <groupId>ca.uhn.hapi.fhir</groupId>
    <artifactId>hapi-fhir-structures-dstu3</artifactId>
    <version>${hapi-fhir.version}</version>
  </dependency>
</project>
FhirContextTest.java:
package com.windofkeltia.fhir;

import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.IParser;
import org.hl7.fhir.dstu3.model.Patient;

import com.windofkeltia.utilities.TestUtilities;

/**
 * Prime this pump: figure out how HAPI FHIR works from our direction: usually, consumers are,
 * unlike us, interested in the client-side or in FHIR servers set up to validate what they
 * produce. We're not here to produce FHIR (XML or JSON), but to parse it and turn it into
 * a meta- or interlingua for a search engine (not shown here).
 *
 * @author Russell Bateman
 * @since February 2020
 */
public class FhirContextTest
{
  // @formatter:off
  @Rule   public TestName name = new TestName();
  @Before public void setUp()  { TestUtilities.setUp( name ); }
  @After  public void tearDown() { }

  private static boolean VERBOSE = true;//TestUtilities.VERBOSE;

  /**
   * The purpose of this case is only to demonstrate the client side of things a little bit
   * (see {@link FhirContextTest} documentation), which isn't what interests us, but doing
   * this does give us an easy way to come up with the XML for a Patient. We could also
   * generate the JSON for a sample Patient. Whatever we made in here, we could use in one
   * of the more germane tests below.
   */
  @Ignore
  @Test
  public void testXmlSerialization()
  {
    FhirContext context = FhirContext.forDstu3();
    IParser     parser  = context.newXmlParser().setPrettyPrint( true );
    Patient     patient = new Patient();
    patient.addName().setFamily( "Simpson" ).addGiven( "James" );
    String serialized = parser.encodeResourceToString( patient );
    System.out.println( "  Patient (serialized):\n" + serialized );
  }

  @Test
  public void testXml()
  {
    FhirContext  context = FhirContext.forDstu3();
    final String INPUT   = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"
                    + "<Patient xmlns=\"http://hl7.org/fhir\">\n"
                    + "  <name>\n"
                    + "    <family value=\"Simpson\" />\n"
                    + "    <given value=\"James\" />\n"
                    + "  </name>\n"
                    + "</Patient>";

    if( VERBOSE )
      System.out.println( "  Input:\n" + INPUT );

    IParser parser  = context.newXmlParser();
    Patient patient = parser.parseResource( Patient.class, INPUT );

    if( VERBOSE )
    {
      System.out.print(   "  " + patient.getName().get( 0 ).getFamily() );
      System.out.println( ", " + patient.getName().get( 0 ).getGiven() );
    }
  }

  @Test
  public void testJson()
  {
    FhirContext  context = FhirContext.forDstu3();
    final String INPUT   = "{"
                    + "  \"resourceType\" : \"Patient\","
                    + "  \"name\" :"
                    + "  ["
                    + "    {"
                    + "      \"family\": \"Simpson\""
                    + "    }"
                    + "  ]"
                    + "}";

    if( VERBOSE )
      System.out.println( "  Input:\n  " + INPUT );

    IParser parser  = context.newJsonParser();
    Patient patient = parser.parseResource( Patient.class, INPUT );

    if( VERBOSE )
    {
      System.out.print(   "  " + patient.getName().get( 0 ).getFamily() );
      System.out.println( ", " + patient.getName().get( 0 ).getGiven() );
    }
  }

  /**
   * Start out in XML, but switch to JSON for output (serialization).
   */
  @Test
  public void testXmlToJson()
  {
    FhirContext  context = FhirContext.forDstu3();
    final String INPUT   = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>"
                    + "<Patient xmlns=\"http://hl7.org/fhir\">"
                    + "  <name>"
                    + "    <family value=\"Simpson\" />"
                    + "    <given value=\"James\" />"
                    + "  </name>"
                    + "</Patient>";

    if( VERBOSE )
      System.out.println( "  Input:\n" + INPUT );

    IParser parser    = context.newXmlParser();
    Patient patient   = parser.parseResource( Patient.class, INPUT );
    parser            = context.newJsonParser().setPrettyPrint( true );
    String serialized = parser.encodeResourceToString( patient );

    if( VERBOSE )
      System.out.println( " Pretty-printed JSON output:\n" + serialized );
  }
}

Sample output from these tests

Test: testXml ----------------------------------------------------------------------------------
[main] INFO ca.uhn.fhir.util.VersionUtil - HAPI FHIR version 4.2.0 - Rev 8491707942
[main] INFO ca.uhn.fhir.context.FhirContext - Creating new FHIR context for FHIR version [DSTU3]
  Input:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Patient xmlns="http://hl7.org/fhir">
  <name>
    <family value="Simpson"/>
    <given value="James"/>
  </name>
</Patient>
  Simpson, [James]
Test: testJson ---------------------------------------------------------------------------------
[main] INFO ca.uhn.fhir.context.FhirContext - Creating new FHIR context for FHIR version [DSTU3]
  Input:
  {  "resourceType" : "Patient",  "name" :  [    {      "family": "Simpson"    }  ]}
  Simpson, []
Test: testXmlToJson ----------------------------------------------------------------------------
[main] INFO ca.uhn.fhir.context.FhirContext - Creating new FHIR context for FHIR version [DSTU3]
  Input:
<?xml version="1.0" encoding="UTF-8" standalone="no"?><Patient xmlns="http://hl7.org/fhir"><name><family value="Simpson"/><given value="James"/></name></Patient>
  Pretty-printed JSON output:
{
  "resourceType": "Patient",
  "name": [
    {
      "family": "Simpson",
      "given": [
        "James"
      ]
    }
  ]
}

HAPI FHIR Instance Validation Validation follows documentation outline beginning 11.0 Validation Introduction. The strategy we're looking for is called, "Instance Validator."

package com.windofkeltia.fhir;

import java.util.List;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.fhirpath.IFhirPath;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.IValidatorModule;
import ca.uhn.fhir.validation.SingleValidationMessage;
import ca.uhn.fhir.validation.ValidationResult;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r5.model.Bundle;

import com.windofkeltia.utilities.TestUtilities;

/**
 * @author Russell Bateman
 * @since April 2021
 */
public class FhirValidationTest
{
  @Rule   public TestName name = new TestName();
  @After  public void tearDown() { }
  @Before public void setUp()    { TestUtilities.setUp( name ); }

  private static final boolean VERBOSE = TestUtilities.VERBOSE;

  private static final String PATIENT_AND_ENCOUNTER = ""    // DO NOT CHANGE THIS!
      + "<Bundle xmlns=\"http://hl7.org/fhir\">\n"
      + "  <type value=\"transaction\"/>\n"
      + "  <entry>\n"
      + "    <fullUrl value=\"urn:uuid:38826a3f-8e5c-4846-95c3-7f12a233447d\"/>\n"
      + "    <resource>\n"
      + "      <Patient xmlns=\"http://hl7.org/fhir\">\n"
      + "        <meta>\n"
      + "          <lastUpdated value=\"2021-01-05T00:00:00.000-06:00\" />\n"
      + "        </meta>\n"
      + "        <identifier>\n"
      + "          <system value=\"https://fhir.acme.io/facility/Beverly Hills Clinic\" />\n"
      + "          <value value=\"3660665800\" />\n"
      + "          <assigner>\n"
      + "            <display value=\"Beverly Hills Clinic\" />\n"
      + "          </assigner>\n"
      + "        </identifier>\n"
      + "        <name>\n"
      + "          <family value=\"Munster\" />\n"
      + "          <given value=\"Herman\" />\n"
      + "        </name>\n"
      + "        <gender value=\"male\" />\n"
      + "        <birthDate value=\"1835-10-31\" />\n"
      + "        <deceasedBoolean value=\"false\" />\n"
      + "        <address>\n"
      + "          <text value=\"1313 Mockingbird Lane, Mockingbird Heights, CA 90210\" />\n"
      + "          <line value=\"1313 Mockingbird Lane\" />\n"
      + "          <city value=\"Mockingbird Heights\" />\n"
      + "          <state value=\"CA\"/>\n"
      + "          <postalCode value=\"90210\" />\n"
      + "        </address>\n"
      + "        <telecom>\n"
      + "          <system value=\"phone\" />\n"
      + "          <value value=\"+3035551212\" />\n"
      + "          <use value=\"home\" />\n"
      + "        </telecom>\n"
      + "      </Patient>\n"
      + "    </resource>\n"
      + "  </entry>\n"
      + "  <entry>\n"
      + "    <resource>\n"
      + "      <Encounter xmlns=\"http://hl7.org/fhir\">\n"
      + "        <id value=\"emerg\" />\n"
      + "        <period>\n"
      + "          <start value=\"2017-02-01T08:45:00+10:00\" />\n"
      + "          <end value=\"2017-02-01T09:27:00+10:00\" />\n"
      + "        </period>\n"
      + "      </Encounter>\n"
      + "    </resource>\n"
      + "    <request>\n"
      + "      <method value=\"POST\" />\n"
      + "      <url value=\"Patient\" />\n"
      + "    </request>\n"
      + "  </entry>\n"
      + "</Bundle>\n";

  @Test
  public void testBundle()
  {
    FhirContext context = FhirContext.forR5();
    IParser     parser  = context.newXmlParser().setPrettyPrint( true );
    IFhirPath   where   = context.newFhirPath();
    Bundle      bundle  = ( Bundle ) parser.parseResource( PATIENT_AND_ENCOUNTER );

    List< IBaseResource > resources = where.evaluate( bundle, "Bundle.entry.resource",  IBaseResource.class );

    if( VERBOSE )
      System.out.println( parser.encodeResourceToString( bundle ) );
  }

  @Test
  public void testBundleOnValidator()
  {
    FhirContext      context   = FhirContext.forR5();
    IParser          parser    = context.newXmlParser().setPrettyPrint( true );
    Bundle           bundle    = ( Bundle ) parser.parseResource( PATIENT_AND_ENCOUNTER );
    IValidatorModule module    = new FhirInstanceValidator( context );
    FhirValidator    validator = context.newValidator().registerValidatorModule( module );
    ValidationResult result    = validator.validateWithResult( bundle );

    if( VERBOSE )
    {
      System.out.println( PATIENT_AND_ENCOUNTER );
      final String PROMPT = "<Bundle ... />";
      for( SingleValidationMessage message : result.getMessages() )
        System.out.println( message.getLocationString() + " " + message.getMessage() );
    }
  }
}

Output from the validator (formatted as a table):

Location Error
1. Bundle bdl-3: entry.request mandatory for batch/transaction/history, allowed for subscription-notification, otherwise prohibited [entry.all(request.exists() = ((%resource.type = 'batch') or (%resource.type = 'transaction') or (%resource.type = 'history'))) or (type = 'subscription-notification')]
2. Bundle.entry[0].resource.ofType(Patient).identifier[0].system URI values cannot have whitespace('https://fhir.acme.io/facility/Beverly Hills Clinic')
3. Bundle.entry[1].resource.ofType(Encounter) Profile http://hl7.org/fhir/StructureDefinition/Encounter, Element 'Bundle.entry[1].resource.ofType(Encounter).status': minimum required = 1, but only found 0
4. Bundle.entry[1].resource.ofType(Encounter) Profile http://hl7.org/fhir/StructureDefinition/Encounter, Element 'Bundle.entry[1].resource.ofType(Encounter).class': minimum required = 1, but only found 0

The main downfall is not to have line numbers. HAPI FHIR has been planning line numbers for time immemorial, but admits that it's a lot of work and won't happen soon.

Legend (to above)

  1. 1. For the Bundle to be valid, there must be a <request .../> element.
  2. 2. See the URL, line 47. It has spaces, which is illegal.
  3. 3. Missing <status ... /> element for the Encounter. status has a cardinality of 1..1.
  4. 4. Missing <class ... /> element for the Encounter. class has a cardinality of 1..1.

Let's experiment with validation-chain support...

This is a new test case for FhirValidationTest above.

  @Test
  public void testBundleOnValidationSupportChain()
  {
    FhirContext      context   = FhirContext.forR5();
    IParser          parser    = context.newXmlParser().setPrettyPrint( true );
    Bundle           bundle    = ( Bundle ) parser.parseResource( PATIENT_AND_ENCOUNTER );
    IValidatorModule module    = new FhirInstanceValidator( context );
    FhirValidator    validator = context.newValidator().registerValidatorModule( module );

    ValidationSupportChain supportChain = new ValidationSupportChain(
      new DefaultProfileValidationSupport( context ),
      new InMemoryTerminologyServerValidationSupport( context ),
      new CommonCodeSystemsTerminologyService( context ) );

    FhirInstanceValidator instanceValidator = new FhirInstanceValidator( supportChain );
                     instanceValidator.setAnyExtensionsAllowed( true );

    ValidationResult result = validator.validateWithResult( bundle );

    if( VERBOSE )
    {
      int count = 1;
      System.out.println( "  Validation was " + result.isSuccessful() );
      System.out.println( PATIENT_AND_ENCOUNTER );
      final String PROMPT = "<Bundle ... />";
      for( SingleValidationMessage message : result.getMessages() )
        System.out.println( StringUtilities.padStringRight( ""+count++, 4 )
                       + message.getSeverity() + " - "
                       + message.getLocationString() + " "
                       + message.getMessage() );
    }
  }

Output

1   ERROR - Bundle bdl-3: entry.request mandatory for batch/transaction/history, allowed for subscription-notification, otherwise prohibited [entry.all(request.exists() = ((%resource.type = 'batch') or (%resource.type = 'transaction') or (%resource.type = 'history'))) or (type = 'subscription-notification')]
2   ERROR - Bundle.entry[0].resource.ofType(Patient).identifier[0].system URI
values cannot have whitespace('https://fhir.acme.io/facility/Beverly Hills Clinic')
3   ERROR - Bundle.entry[1].resource.ofType(Encounter) Profile http://hl7.org/fhir/StructureDefinition/Encounter, Element 'Bundle.entry[1].resource.ofType(Encounter).status': minimum required = 1, but only found 0
4   ERROR - Bundle.entry[1].resource.ofType(Encounter) Profile http://hl7.org/fhir/StructureDefinition/Encounter, Element 'Bundle.entry[1].resource.ofType(Encounter).class': minimum required = 1, but only found 0

FHIR validation

I left some nicer bread crumbs on validation here.


Populating a FHIR Patient from scratch

This is a switch from extracting information from and validating existing FHIR. To follow this code, you must imagine a POJO, IxmlPatient, that contains information recorded in another framework from which a FHIR patient is to be created.

Some of this is simplified to keep the example short, but it's fairly complete as compared to HAPI FHIR documentation.

import java.util.ArrayList;
import java.util.List;

import org.hl7.fhir.r5.model.BooleanType;
import org.hl7.fhir.r5.model.DataType;
import org.hl7.fhir.r5.model.IntegerType;
import org.hl7.fhir.r5.model.Patient;

import com.windofkeltia.ixml.pojos.IxmlPatient;

public class PopulatePatient
{
  private final Patient patient;

  public Patient get() { return patient; }

  public PopulatePatient( IxmlPatient ixmlPojo )
  {
    Identifier identifier = new Identifier();
    identifier.setUse( Identifier.IdentifierUse.OFFICIAL );
    identifier.setValue( mrn );
    patient.addIdentifier( identifier.get() );

    patient.setActive( true );

    name.setUse( HumanName.NameUse.OFFICIAL );
    name.setFamily( pojo.lastname );
    List< String > givens = new ArrayList<>();
    givens.add( pojo.firstname );
    givens.addAll( pojo.middlename );
    for( String given : gives )
      name.addGiven( given );
    name.addPrefix( pojo.prefix );
    name.addSuffix( pojo.suffix );
    patient.addName( name );

    ContactPoint telecom = new ContactPoint();
    telecom.setUse( ContactPoint.ContactPointUse.HOME );
    telecom.setSystem( ContactPoint.ContactPointSystem.PHONE );
    telecom.setValue( pojo.phone );
    telecom.setRank( 1 );
    patient.addTelecom( telecom );

    patient.setGender( ( pojo.gender.equals( "F" ) )
                        ? Enumerations.AdministrativeGender.FEMALE
                        : Enumerations.AdministrativeGender.MALE );

    patient.setBirthDate( makeDateFromString( pojo.birthdate ) );

    patient.setDeceased( new BooleanType( false ) );

    Address address = new Address();
    address.setUse( Address.AddressUse.HOME );
    address.setType( Address.AddressType.POSTAL );
    address.addLine( pojo.street );
    address.setCity( pojo.city );
    address.setState( pojo.state );
    address.setPostalCode( pojo.zipcode );
    address.setCountry( pojo.country );
    patient.addAddress( address );

    patient.setMaritalStatus( ( pojo.maritalstatus.equals( "married" ) ) ? "M" : "U" );

    if( pojo.multipleBirth != 0 )
    {
      patient.setMultipleBirth( new BooleanType( true ) );
      patient.setMultipleBirth( new IntegerType( pojo.multipleBirth ) );
    }

    Patient.ContactComponent contact = new Patient.ContactComponent();
    CodeableConcept codeableConcept  = new CodeableConcept();
    Coding          coding           = new Coding();
    coding.setCode( "N" );
    coding.setSystem( "http://terminology.hl7.org/CodeSystem/v2-0131" );
    coding.setDisplay( "next-of-kin" );
    coding.setVersion( "2.9" );
    codeableConcept.addCoding( coding );
    contact.addRelationship( codeableConcept );
    contact.setName( name );
    contact.addTelecom( telecom );
    contact.setAddress( address );
    contact.setGender( ( pojo.nok.gender.equals( "F" ) )
                        ? Enumerations.AdministrativeGender.FEMALE
                        : Enumerations.AdministrativeGender.MALE );
    patient.addContact( contact );

    Patient.PatientCommunicationComponent communication = new Patient.PatientCommunicationComponent();
    CodeableConcept languageCodeablConcept = new CodeableConcept();
    Coding          languageCoding         = new Coding();
    languageCoding.setSystem( "http://hl7.org/fhir/ValueSet/all-languages" );
    languageCoding.setVersion( "4.01" );
    languageCoding.setDisplay( pojo.preferredlanguage );
    communication.setLanguage( languageCodeableConcept );
    communication.setPreferred( true );

    // need to add reference to 'generalPractitioner' (Organization|Practitioner|PractitionerRole)
    // need to add reference to 'managingOrganization' (Organization)
  }

I cheat a little bit above because I inherit patient and other data in an intermediate form I call IXML whose origin is in old work with HL7 v3. I should give background to my examples by showing it though it's super simple. Here's what a minimal patient looks like. (No PHI here—all of it is made up.)

package com.windofkeltia.ixml;

import java.util.Collections;

import com.windofkeltia.ixml.pojos.IxmlAddress;
import com.windofkeltia.ixml.pojos.IxmlLanguage;
import com.windofkeltia.ixml.pojos.IxmlMaritalStatus;
import com.windofkeltia.ixml.pojos.IxmlPatient;
import com.windofkeltia.ixml.pojos.IxmlReligion;
import com.windofkeltia.ixml.pojos.IxmlSex;

public class IxmlDataMockUps
{
  public static IxmlPatient makePatient()
  {
    IxmlPatient patient   = new IxmlPatient();
    patient.facilityoid   = "2.16.840.1.113883.3.6452";
    patient.mrn           = "665892";
    patient.birthdate     = "1827-04-01";
    patient.firstname     = "Lily";
    patient.lastname      = "Munster";
    patient.gender        = new IxmlSex().withGender( "F" );
    patient.language      = new IxmlLanguage().withLanguage( "English" );
    patient.maritalstatus = new IxmlMaritalStatus().withMaritalStatus( "MARRIED" );
    patient.religion      = new IxmlReligion().withReligion( "LTH" );

    IxmlAddress address = new IxmlAddress( "address" );
    address.line        = Collections.singletonList(  "1313 Mockingbird Lane" );
    address.city        = "Mockingbird Heights";
    address.state       = "CA";
    address.zipcode     = "90210";
    address.country     = "US";
    patient.address     = address;

    return patient;
  }
  ...
}

Rolling a Patient into a Bundle

We'll take the FHIR Patient we created earlier and embed it in a FHIR Bundle. Why is this interesting? Because there are fields in the bundle to set up and also an entry/resource envelop the patient must live in.

public class PopulateBundleTest
{
  private static final boolean VERBOSE = true;

  @Test
  public void wrapWithResourceEntry() throws FhirServerException
  {
    Patient   patient = new PopulatePatient( ixmlPojo ).generate();
    Bundle    bundle  = new Bundle();

    Identifier identifier = new Identifier();
    identifier.setUse( Identifier.IdentifierUse.OFFICIAL );
    identifier.setValue( patient.getIdentifierFirstRep().getValue() );
    bundle.setIdentifier( identifier );

    bundle.setType( Bundle.BundleType.SEARCHSET );

    BundleEntryRequestComponentBuilder requestBuilder = new BundleEntryRequestComponentBuilder.Builder()
        .method( "get" )
        .url( "this is the request URL" )
        .ifMatch( "if match" )
        .build();

    BundleEntryComponentBuilder entryBuilder = new BundleEntryComponentBuilder.Builder()
        .fullUrl( "this is a URL" )
        .resource( patient )
        .search( "match" )
        .request( requestBuilder.get() )
        .build();
    bundle.addEntry( entryBuilder.get() );

    // encode into XML string and write this out...
    HapiFhirInstance instance    = HapiFhirInstance.getInstance();
    IParser          parser      = instance.getFhirXmlParser();
    String           FHIR_OUTPUT = parser.setPrettyPrint( true ).encodeResourceToString( bundle );

    if( VERBOSE )
      System.out.println( FHIR_OUTPUT );
  }
}

Notes

For building the Bundle request...

package com.windofkeltia.resources;

import org.hl7.fhir.r4.model.Bundle;

import com.windofkeltia.FhirServerException;
import com.windofkeltia.utilities.StringUtilities;

public class BundleEntryRequestComponentBuilder
{
  private final Bundle.BundleEntryRequestComponent request = new Bundle.BundleEntryRequestComponent();

  private final String method;
  private final String url;
  private final String ifNoneMatch;
  private final String ifModifiedSince;
  private final String ifMatch;
  private final String ifNoneExist;

  public Bundle.BundleEntryRequestComponent get() throws FhirServerException
  {
    request.setMethod( CodesAndSystems.httpVerb( method ) );
    request.setUrl( url );
    if( !StringUtilities.isEmpty( ifNoneMatch ) )
      request.getIfNoneMatch();
    if( !StringUtilities.isEmpty( ifModifiedSince ) )
      request.getIfModifiedSince();
    if( !StringUtilities.isEmpty( ifMatch ) )
      request.getIfMatch();
    if( !StringUtilities.isEmpty( ifNoneExist ) )
      request.getIfNoneExist();
    return request;
  }

  protected BundleEntryRequestComponentBuilder( Builder builder )
  {
    method          = builder.method;
    url             = builder.url;
    ifNoneMatch     = builder.ifNoneMatch;
    ifModifiedSince = builder.ifModifiedSince;
    ifMatch         = builder.ifMatch;
    ifNoneExist     = builder.ifNoneExist;
  }

  public static class Builder
  {
    private String method;
    private String url;
    private String ifNoneMatch;
    private String ifModifiedSince;
    private String ifMatch;
    private String ifNoneExist;

    public Builder method         ( final String METHOD )          { method          = METHOD;          return this; }
    public Builder url            ( final String URL )             { url             = URL;             return this; }
    public Builder ifNoneMatch    ( final String IFNONEMATCH )     { ifNoneMatch     = IFNONEMATCH;     return this; }
    public Builder ifModifiedSince( final String IFMODIFIEDSINCE ) { ifModifiedSince = IFMODIFIEDSINCE; return this; }
    public Builder ifMatch        ( final String IFMATCH )         { ifMatch         = IFMATCH;         return this; }
    public Builder ifNoneExist    ( final String IFNONEEXIST )     { ifNoneExist     = IFNONEEXIST;     return this; }
    public BundleEntryRequestComponentBuilder build()              { return new BundleEntryRequestComponentBuilder( this ); }
  }
}

For building the Bundle entry component...

public class BundleEntryComponentBuilder
{
  private final Bundle.BundleEntryComponent entry = new Bundle.BundleEntryComponent();

  private final String                              link;
  private final String                              fullUrl;
  private final Resource                            resource;
  private final String                              search;
  private final Bundle.BundleEntryRequestComponent  request;
  private final Bundle.BundleEntryResponseComponent response;

  @Override
  public Bundle.BundleEntryComponent get() throws FhirServerException
  {
    if( !StringUtilities.isEmpty( fullUrl ) )
      entry.setFullUrl( fullUrl );
    if( nonNull( resource ) )
      entry.setResource( resource );
    if( !StringUtilities.isEmpty( search ) )
    {
      /* Bundle.SearchEntryMode.MATCH if the thing matched or
       * Bundle.SearchEntryMode.INCLUDE if added because referenced by the thing matched.
       * Bundle.entry.search.score of 1L means "most relevant."
       */
      Bundle.BundleEntrySearchComponent searchComponent = new Bundle.BundleEntrySearchComponent();
      searchComponent.setMode( CodesAndSystems.searchMode( search ) );
      searchComponent.setScore( 1L );
      entry.setSearch( searchComponent );
    }
    if( nonNull( request ) )
      entry.setRequest( request );
    if( nonNull( response ) )
      entry.setResponse( response );
    return entry;
  }

  ...
}

FHIR_OUTPUT is something like this. This isn't much of a bundle, but we're getting there.

<Bundle xmlns="http://hl7.org/fhir">
  <identifier>
    <use value="?"/>
    <type>
      <coding>
        <userSelected value="false"/>
      </coding>
    </type>
    <value value="665892"/>
  </identifier>
  <type value="searchset"/>
  <entry>
    <fullUrl value="this is a URL"/>
    <resource>
      <Patient xmlns="http://hl7.org/fhir">
        <identifier>
          <use value="?"/>
          <type>
            <coding>
              <userSelected value="false"/>
            </coding>
          </type>
          <value value="665892"/>
        </identifier>
        <active value="true"/>
        <name>
          <use value="usual"/>
          <family value="Munster"/>
          <given value="Lily"/>
        </name>
        <gender value="female"/>
        <birthDate value="1827-03-31"/>
        <deceasedBoolean value="false"/>
        <address>
          <use value="home"/>
          <type value="postal"/>
          <line value="1313 Mockingbird Lane"/>
          <city value="Mockingbird Heights"/>
          <state value="CA"/>
          <postalCode value="90210"/>
          <country value="US"/>
        </address>
      </Patient>
    </resource>
    <search>
      <mode value="match"/>
      <score value="1"/>
    </search>
    <request>
      <method value="GET"/>
      <url value="this is the request URL"/>
    </request>
  </entry>
</Bundle>

Extracting data from a Patient

This is far form perfect and it's got a few problems, but it's a useful introduction to how it works using HAPI FHIR.

package com.windofkeltia.processor.helpers;

import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Patient;

import com.windofkeltia.fhir.FhirAddress;
import com.windofkeltia.fhir.FhirDate;
import com.windofkeltia.fhir.FhirLanguage;
import com.windofkeltia.fhir.FhirName;
import com.windofkeltia.fhir.FhirPatient;
import com.windofkeltia.fhir.FhirTelecom;
import com.windofkeltia.utilities.StringUtilities;

public class PatientExtractor
{
  private final Patient patient;
  public        String  patient_date_of_birth;
  public        String  patient_death_flag;
  public        String  patient_death_timestamp;
  public        String  patient_marital_status;
  public        String  patient_language;
  public        String  full_name;
  public        String  patient_telecom;
  public        String  patient_address;
  public        String  patient_ssn;
  public        String  patient_race;
  public        String  patient_ethnicity;
  public        String  patient_religion;

  public PatientExtractor( IBaseResource resource ) { patient = ( Patient ) resource; }

  public void extract( final String[] paths )
  {
    for( String path : paths )
    {
      switch( path )
      {
        case "Patient.birthDate" :
          patient_date_of_birth = patient.getBirthDateElement().getValueAsString();
          break;
        case "Patient.deceasedBoolean" :
          patient_death_flag = String.valueOf( patient.getDeceasedBooleanType().booleanValue() );
          break;
        case "Patient.deceasedDateTime" :
          patient_death_flag = new FhirDate( patient.getDeceasedDateTimeType().getValue() ).toString();
          break;
        case "Patient.maritalStatus" :
          patient_marital_status = patient.getMaritalStatus().getCodingFirstRep().getCode();
          break;
        case "Patient.communication.language" :
          String language = new FhirLanguage( patient.getCommunicationFirstRep() ).getCode();
          if( !StringUtilities.isEmpty( language ) )
            patient_language = language;
          break;
        case "Patient.name" :
          FhirName name = new FhirName( patient.getNameFirstRep() );
          if( !StringUtilities.isEmpty( name.full ) )
            full_name = name.full;
          break;
        case "Patient.telecom" :
          FhirTelecom telecom = new FhirTelecom( patient.getTelecomFirstRep() );
          patient_telecom = telecom.value;
          break;
        case "Patient.address" :
          FhirAddress address = new FhirAddress( patient.getAddressFirstRep() );
          patient_address = address.toString();
          break;
        case "Patient.ssn" :
          if( !StringUtilities.isEmpty( new FhirPatient( patient ).getSsn() ) )
            patient_ssn = ssn;
          break;
        case "Patient.race" :
          String race = new FhirPatient( patient ).getRace();
          if( !StringUtilities.isEmpty( race ) )
            patient_race = race;
          break;
        case "Patient.ethnicity" :
          String ethnicity = new FhirPatient( patient ).getEthnicity();
          if( !StringUtilities.isEmpty( ethnicity ) )
            patient_ethnicity = ethnicity;
          break;
        case "Patient.religion" :
          String religion = new FhirPatient( patient ).getReligion();
          if( !StringUtilities.isEmpty( religion ) )
            patient_religion = religion;
          break;
        default :
          break;
      }
    }
  }
}
FhirPatient.java:
package com.windofkeltia.fhir;

import java.util.List;

import static java.util.Objects.isNull;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.IParser;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Extension;
import org.hl7.fhir.r4.model.Identifier;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Resource;

public class FhirPatient
{
  private Patient patient;

  public FhirPatient( Patient patient ) { this.patient = patient; }

  private static final String SSN_CODING_URL   = "http://terminology.hl7.org/CodeSystem/v2-0203";
  private static final String SSN_CODING_VALUE = "SS";
  private static final String SSN_SYSTEM_URL   = "http://hl7.org/fhir/sid/us-ssn";
  /**
   * <identifier>
   *   <type>
   *     <coding>
   *       <system value="http://terminology.hl7.org/CodeSystem/v2-0203" />   SSN_CODING_URL
   *       <code value="SS" />                                                SSN_CODING_VALUE
   *     </coding>
   *   </type>
   *   <system value="http://hl7.org/fhir/sid/us-ssn" />                      SSN_SYSTEM_URL
   *   <value value="500-12-3456" />
   * </identifier>
   */
  public String getSsn()
  {
    if( isNull( patient ) )
      return null;

    for( Identifier identifier : patient.getIdentifier() )
    {
      if( !identifier.hasType() || !identifier.hasSystem() || !identifier.hasValue() )
        continue;

      CodeableConcept concept = identifier.getType();         if( !concept.hasCoding() )                     continue;
      Coding          coding  = concept.getCodingFirstRep();  if( !coding.hasSystem() || !coding.hasCode() ) continue;
      if( !coding.getSystem().equals( SSN_CODING_URL ) )
        continue;
      if( !coding.getCode().equals( SSN_CODING_VALUE ) )
        continue;
      if( !identifier.getSystem().equals( SSN_SYSTEM_URL ) )
        continue;
      return identifier.getValue();
    }

    return null;
  }

  private static final String RACE_EXTENSION_URL = "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race";
  private static final String RACE_SYSTEM        = "urn:oid:2.16.840.1.113883.6.238";
  /**
   * <extension url="http://hl7.org/fhir/us/core/StructureDefinition/us-core-race">
   *   <valueCoding>
   *     <system value="urn:oid:2.16.840.1.113883.6.238" />
   *     <code value="2106-3" />
   *     <display value="White" />
   *   </valueCoding>
   * </extension>
   */
  public String getRace()
  {
    if( isNull( patient ) )
      return null;

    Extension race = patient.getExtensionByUrl( RACE_EXTENSION_URL );

    if( !race.hasValue() )
      return null;

    Coding coding = ( Coding ) race.getValue();

    return coding.getCode() + "(" coding.getDisplay() + ")";
  }

  private static final String ETHNICITY_EXTENSION_URL = "http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity";
  private static final String ETHNICITY_SYSTEM        = RACE_SYSTEM;
  /**
   * <extension url="http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity">
   *   <valueCoding>
   *     <system value="urn:oid:2.16.840.1.113883.6.238" />
   *     <code value="2186-5"/>
   *     <display value="Not Hispanic or Latino" />
   *   </valueCoding>
   * </extension>
   */
  public String getEthnicity()
  {
    if( isNull( patient ) )
      return null;

    Extension ethnicity = patient.getExtensionByUrl( ETHNICITY_EXTENSION_URL );

    if( !ethnicity.hasValue() )
      return null;

    Coding coding = ( Coding ) ethnicity.getValue();

    return coding.getCode() + "(" coding.getDisplay() + ")";
  }

  private static final String RELIGION_EXTENSION_URL = "http://hl7.org/fhir/us/core/StructureDefinition/patient-religion";
  private static final String RELIGION_SYSTEM        = "http://terminology.hl7.org/CodeSystem/v3-ReligiousAffiliation";
  /**
   * <extension url="http://hl7.org/fhir/us/core/StructureDefinition/patient-religion">
   *   <valueCoding>
   *     <system value="http://terminology.hl7.org/CodeSystem/v3-ReligiousAffiliation" />
   *     <code value="1027" />
   *     <display value="Latter Day Saints" />
   *   </valueCoding>
   * </extension>
   */
  public String getReligion()
  {
    if( isNull( patient ) )
      return null;

    Extension religion = patient.getExtensionByUrl( RELIGION_EXTENSION_URL );

    if( !religion.hasValue() )
      return null;

    Coding coding = ( Coding ) religion.getValue();

    return coding.getCode() + "(" coding.getDisplay() + ")";
  }
}

Extracting polymorphic onset[x] data using FHIRPath from an AllergyIntolerance

I'd have thought some Crayola® brighter than I would drop such breadcrumbs as these, but I guess I'm the first. I won't explain exactly what I'm doing here because if you find it useful, it's because you need it and already grok the problem, then went googling to find this page.

In some DataTypes I "show my work," that is, fudging around until I figure out what to do. I hope this isn't too confusing. My production code doesn't do that, obviously.

Basically, I have discovered that neither period nor range work nor are particularly useful. I'll revisit this down the road if my customers need those representations.

package com.windofkeltia.hapi.fhir;

import java.time.LocalDate;
import java.time.Period;
import java.time.ZoneId;
import java.util.Date;
import java.util.List;

import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.FixMethodOrder;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.junit.runners.MethodSorters;

import ca.uhn.fhir.model.api.IElement;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.IParser;

import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.hapi.ctx.HapiWorkerContext;
import org.hl7.fhir.r5.model.Age;
import org.hl7.fhir.r5.model.AllergyIntolerance;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.DateTimeType;
import org.hl7.fhir.r5.model.Range;
import org.hl7.fhir.r5.utils.FHIRPathEngine;

import com.windofkeltia.utilities.TestUtilities;

@FixMethodOrder( MethodSorters.JVM )
public class AllergyIntoleranceTest
{
  @Rule   public TestName name = new TestName();
  @After  public void tearDown() { }
  @Before public void setUp()    { TestUtilities.setUp( name ); }

  @BeforeClass public static void setUpClass()
  {
    context    = FhirContext.forR5();
    parser     = context.newXmlParser().setPrettyPrint( true );
    validation = new DefaultProfileValidationSupport( context );
    worker     = new HapiWorkerContext( context, validation );
    fhirEngine = new FHIRPathEngine( worker );
  }

  private static FhirContext                     context;
  private static IParser                         parser;
  private static DefaultProfileValidationSupport validation;
  private static IWorkerContext                  worker;
  private static FHIRPathEngine                  fhirEngine;
  private        AllergyIntolerance              allergy;
  private        List< Base >                    values;

  //<editor-fold desc="Tiny allergy-intolerance bundle...">
  private static final String ALLERGYINTOLERANCE_1 = ""
    + "<AllergyIntolerance xmlns=\"http://hl7.org/fhir\">\n"
    + "  <identifier value=\"example\">\n"
    + "    <system value=\"http://acme.com/ids/patients/risks\"/>\n"
    + "    <value value=\"49476534\"/>\n"
    + "  </identifier>\n"
    + "  <recordedDate value=\"2014-10-09T14:58:00+11:00\"/>\n"
    + "  <recorder>\n"
    + "    <reference value=\"Practitioner/example\"/>\n"
    + "  </recorder>\n"
    + "  <patient>\n"
    + "    <reference value=\"Patient/example\"/>\n"
    + "  </patient>\n"
    + "  <substance>\n"
    + "    <coding>\n"
    + "      <system value=\"http://snomed.info/sct\"/>\n"
    + "      <code value=\"227493005\"/>\n"
    + "      <display value=\"Cashew nuts\"/>\n"
    + "    </coding>\n"
    + "  </substance>\n"
    + "  <status value=\"confirmed\"/>\n"
    + "  <criticality value=\"low\"/>\n"
    + "  <type value=\"allergy\"/>\n"
    + "  <code>\n"
    + "    <coding>\n"
    + "      <system value=\"http://snomed.info/sct\"/>\n"
    + "      <code value=\"387207008\"/>\n"
    + "      <display value=\"Ibuprofen\"/>\n"
    + "    </coding>\n"
    + "  </code>\n"
    + "  <category value=\"food\"/>\n";
  private static final String ALLERGYINTOLERANCE_2 = ""
    + "  <lastOccurence value=\"2012-06\"/>\n"
    + "  <reaction>\n"
    + "    <substance>\n"
    + "      <coding>\n"
    + "        <system value=\"http://www.nlm.nih.gov/research/umls/rxnorm\"/>\n"
    + "        <code value=\"C3214954\"/>\n"
    + "        <display value=\"cashew nut allergenic extract Injectable Product\"/>\n"
    + "      </coding>\n"
    + "    </substance>\n"
    + "    <manifestation>\n"
    + "      <coding>\n"
    + "        <system value=\"http://snomed.info/sct\"/>\n"
    + "        <code value=\"39579001\"/>\n"
    + "        <display value=\"Anaphylactic reaction\"/>\n"
    + "      </coding>\n"
    + "    </manifestation>\n"
    + "    <description value=\"Challenge Protocol. Severe Reaction to 1/8 cashew. Epinephrine administered\"/>\n"
    + "    <onset value=\"2012-06-12\"/>\n"
    + "    <severity value=\"severe\"/>\n"
    + "  </reaction>\n"
    + "  <reaction>\n"
    + "    <certainty value=\"likely\"/>\n"
    + "    <manifestation>\n"
    + "      <coding>\n"
    + "        <system value=\"http://snomed.info/sct\"/>\n"
    + "        <code value=\"64305001\"/>\n"
    + "        <display value=\"Urticaria\"/>\n"
    + "      </coding>\n"
    + "    </manifestation>\n"
    + "    <onset value=\"2004\"/>\n"
    + "    <severity value=\"moderate\"/>\n"
    + "  </reaction>\n"
    + "</AllergyIntolerance>\n";
  //</editor-fold>

  private static final String FHIRPATH = "AllergyIntolerance.onset";

  @Test
  public void testFhirPath_x_datetime()
  {
    final String ONSET_DATETIME = "  <onsetDateTime value=\"2012-06-12\" />\n";

    String ALLERGYINTOLERANCE = ALLERGYINTOLERANCE_1 + ONSET_DATETIME + ALLERGYINTOLERANCE_2;
    allergy = parser.parseResource( AllergyIntolerance.class, ALLERGYINTOLERANCE );
    values  = fhirEngine.evaluate( allergy, FHIRPATH );
    System.out.println( yields( ONSET_DATETIME ) );
    System.out.println( "  " + ( ( DateTimeType ) values.get( 0 ) ).asStringValue() );
    // Output: 2012-06-12
  }

  @Test
  public void testFhirPath_x_age()
  {
    final String ONSET_AGE = ""
      + "  <onsetAge>\n"
      + "    <value value=\"89\" />\n"
      + "  </onsetAge>\n";

    String ALLERGYINTOLERANCE = ALLERGYINTOLERANCE_1 + ONSET_AGE + ALLERGYINTOLERANCE_2;
    allergy = parser.parseResource( AllergyIntolerance.class, ALLERGYINTOLERANCE );
    values  = fhirEngine.evaluate( allergy, FHIRPATH );
    Age age = ( Age ) values.get( 0 );
    System.out.println( yields( ONSET_AGE ) );
    System.out.println( "  " + age.getValue() );
    // Output: 89
  }

  @Test
  public void testFhirPath_x_period()
  {
    final String ONSET_PERIOD = ""
      + "  <onsetPeriod>\n"
      + "    <start value=\"2012-06-12\" />\n"
      + "    <end value=\"2012-07-01\" />\n"
      + "  </onsetPeriod>\n";
    String ALLERGYINTOLERANCE = ALLERGYINTOLERANCE_1 + ONSET_PERIOD + ALLERGYINTOLERANCE_2;
    allergy = parser.parseResource( AllergyIntolerance.class, ALLERGYINTOLERANCE );
    values  = fhirEngine.evaluate( allergy, FHIRPATH );
    org.hl7.fhir.r5.model.Period fhirPeriod = ( org.hl7.fhir.r5.model.Period ) values.get( 0 );
    String start  = fhirPeriod.getStart().toString();
    String end    = fhirPeriod.getEnd().toString();
    Period period = Period.between( toLocalDate( fhirPeriod.getStart() ), toLocalDate( fhirPeriod.getEnd() ) );
    System.out.println( yields( ONSET_PERIOD ) );
    System.out.println( "  " + period );
    // Output: P19D
  }

  private LocalDate toLocalDate( Date date )
  {
    return date.toInstant().atZone( ZoneId.systemDefault() ).toLocalDate();
  }

  @Test
  public void testFhirPath_x_range()
  {
    /* I've tried doing the XML differently (subsuming a value element
     * and even adding a units element under each of low and high elements),
     * --a little like what ended up working for Age, but the HAPI FHIR
     * parser throws an exception for everything but this representation:
     */
    final String ONSET_RANGE = ""
      + "  <onsetRange>\n"
      + "    <low value=\"2012-06-12\" />\n"
      + "    <high value=\"2012-07-01\" />\n"
      + "  </onsetRange>\n";
    String ALLERGYINTOLERANCE = ALLERGYINTOLERANCE_1 + ONSET_RANGE + ALLERGYINTOLERANCE_2;
    allergy = parser.parseResource( AllergyIntolerance.class, ALLERGYINTOLERANCE );
    values  = fhirEngine.evaluate( allergy, FHIRPATH );
    /* No matter, values has depth (type Range), but nulls throughout
     * every field. Alas.
     */
    Base   base  = values.get( 0 );
    IElement  r = ( IElement ) base;
    Range range = ( Range ) r;
    String low = null, high = null;
    if( range.hasLow() )
      low = String.valueOf( range.getLow() );
    if( range.hasHigh() )
      high = String.valueOf( range.getHigh() );
    System.out.println( yields( ONSET_RANGE ) );
    System.out.println( "  " + "[ " + low + " .. " + high + " ]" );
    // Output: [ null .. null ]
  }

  @Test
  public void testFhirPath_x_string()
  {
    final String ONSET_STRING = "  <onsetString value=\"2012-06-12\" />\n";

    String ALLERGYINTOLERANCE = ALLERGYINTOLERANCE_1 + ONSET_STRING + ALLERGYINTOLERANCE_2;
    allergy = parser.parseResource( AllergyIntolerance.class, ALLERGYINTOLERANCE );
    values  = fhirEngine.evaluate( allergy, FHIRPATH );
    System.out.println( yields( ONSET_STRING ) );
    System.out.println( values.get( 0 ) );
    // Output: 2012-06-12
  }

  private String yields( final String ONSET_XML )
  {
    return ONSET_XML.substring( 0, ONSET_XML.length()-1 ) + ':';
  }
}