4.4REST Operations: Overview

 

This page shows the operations which can be implemented on HAPI Plain Server, as well as on the Annotation Client. Most of the examples shown here show how to implement a server method, but to perform an equivalent call on an annotation client you simply put a method with the same signature in your client interface.

4.4.1Instance Level - Read

 

The read operation retrieves a resource by ID. It is annotated with the @Read annotation, and has at least a single parameter annotated with the @IdParam annotation.

@Read()
public Patient getResourceById(@IdParam IdType theId) {
   Patient retVal = new Patient();
   
   // ...populate...
   retVal.addIdentifier().setSystem("urn:mrns").setValue("12345");
   retVal.addName().setFamily("Smith").addGiven("Tester").addGiven("Q");
   // ...etc...
   
   // if you know the version ID of the resource, you should set it and HAPI will 
   // include it in a Content-Location header
   retVal.setId(new IdType("Patient", "123", "2"));
   
   return retVal;
}

Example URL to invoke this method: http://fhir.example.com/Patient/111

The following snippet shows how to define a client interface to handle a read method.

private interface IPatientClient extends IBasicClient
{
  /** Read a patient from a server by ID */
  @Read
  Patient readPatient(@IdParam IdType theId);

  // Only one method is shown here, but many methods may be 
  // added to the same client interface!
}

4.4.2Instance Level - VRead

 

The vread operation retrieves a specific version of a resource with a given ID. To support vread, simply add "version=true" to your @Read annotation. This means that the read method will support both "Read" and "VRead". The IdType instance passed into your method may or may not have the version populated depending on the client's request.

@Read(version=true)
public Patient readOrVread(@IdParam IdType theId) {
   Patient retVal = new Patient();

   if (theId.hasVersionIdPart()) {
      // this is a vread   
   } else {
      // this is a read
   }

   // ...populate...
   
   return retVal;
}

Example URL to invoke this method: http://fhir.example.com/Patient/111/_history/2

4.4.3Instance Level - Update

 

The update operation updates a specific resource instance (using its ID), and optionally accepts a version ID as well (which can be used to detect version conflicts).

Update methods must be annotated with the @Update annotation, and have a parameter annotated with the @ResourceParam annotation. This parameter contains the resource instance to be created. See the @ResourceParam for information on the types allowed for this parameter (resource types, String, byte[]).

In addition, the method may optionally have a parameter annotated with the @IdParam annotation, or they may obtain the ID of the resource being updated from the resource itself. Either way, this ID comes from the URL passed in.

Update methods must return an object of type MethodOutcome. This object contains the identity of the created resource.

The following snippet shows how to define an update method on a server:

Etag
@Update
public MethodOutcome update(@IdParam IdType theId, @ResourceParam Patient thePatient) {
   String resourceId = theId.getIdPart();
   String versionId = theId.getVersionIdPart(); // this will contain the ETag
   
   String currentVersion = "1"; // populate this with the current version
   
   if (!versionId.equals(currentVersion)) {
      throw new ResourceVersionConflictException("Expected version " + currentVersion);
   }
   
   // ... perform the update ...
   return new MethodOutcome();
   
}

Example URL to invoke this method (this would be invoked using an HTTP PUT, with the resource in the PUT body): http://fhir.example.com/Patient

The following snippet shows how the corresponding client interface would look:

@Update
public abstract MethodOutcome updateSomePatient(@IdParam IdType theId, @ResourceParam Patient thePatient);

Conditional Updates

If you wish to support conditional updates, you can add a parameter tagged with a @ConditionalUrlParam annotation. If the request URL contains search parameters instead of a resource ID, then this parameter will be populated.

@Update
public MethodOutcome updatePatientConditional(
      @ResourceParam Patient thePatient, 
      @IdParam IdType theId, 
      @ConditionalUrlParam String theConditional) {

   // Only one of theId or theConditional will have a value and the other will be null,
   // depending on the URL passed into the server. 
   if (theConditional != null) {
      // Do a conditional update. theConditional will have a value like "Patient?identifier=system%7C00001"
   } else {
      // Do a normal update. theId will have the identity of the resource to update
   }
   
   return new MethodOutcome(); // populate this
}

Example URL to invoke this method (this would be invoked using an HTTP PUT, with the resource in the PUT body): http://fhir.example.com/Patient?identifier=system%7C00001

Accessing The Raw Resource Payload

If you wish to have access to the raw resource payload as well as the parsed value for any reason, you may also add parameters which have been annotated with the @ResourceParam of type String (to access the raw resource body) and/or EncodingEnum (to determine which encoding was used).

The following example shows how to use these additional data elements.

@Update
public MethodOutcome updatePatientWithRawValue (
    @ResourceParam Patient thePatient, 
    @IdParam IdType theId, 
    @ResourceParam String theRawBody,
    @ResourceParam EncodingEnum theEncodingEnum) {

   // Here, thePatient will have the parsed patient body, but
   // theRawBody will also have the raw text of the resource 
   // being created, and theEncodingEnum will tell you which
   // encoding was used
 
 return new MethodOutcome(); // populate this
}

Prefer Header / Returning the resource body

If you want to allow clients to request that the server return the resource body as a result of the transaction, you may wish to return the updated resource in the returned MethodOutcome.

In this type of request, the client adds a header containing Prefer: return=representation which indicates to the server that the client would like the resource returned in the response.

In order for the server to be able to honour this request, the server method should add the updated resource to the MethodOutcome object being returned, as shown in the example below.

@Update public MethodOutcome updatePatientPrefer(
   @ResourceParam Patient thePatient, 
   @IdParam IdType theId) {

  // Save the patient to the database
  
  // Update the version and last updated time on the resource
  IdType updatedId = theId.withVersion("123");
  thePatient.setId(updatedId);
  InstantType lastUpdated = InstantType.withCurrentTime();
  thePatient.getMeta().setLastUpdatedElement(lastUpdated);
  
  // Add the resource to the outcome, so that it can be returned by the server
  // if the client requests it
  MethodOutcome outcome = new MethodOutcome();
  outcome.setId(updatedId);
  outcome.setResource(thePatient);
  return outcome;
}

Contention Aware Updating

As of FHIR DSTU2, FHIR uses the ETag header to provide contention aware updating. Under this scheme, a client may create a request that contains an ETag specifying the version, and the server will fail if the given version is not the latest version.

Such a request is shown below. In the following example, the update will only be applied if resource "Patient/123" is currently at version "3". Otherwise, it will fail with an HTTP 409 Conflict error.

Content-Type: application/fhir+json

{ ..resource body.. }

If a client performs a contention aware update, the ETag version will be placed in the version part of the IdDt/IdType that is passed into the method. For example:

@Update public MethodOutcome update(@IdParam IdType theId, @ResourceParam Patient thePatient) {
   String resourceId = theId.getIdPart();
   String versionId = theId.getVersionIdPart(); // this will contain the ETag
   
   String currentVersion = "1"; // populate this with the current version
   
   if (!versionId.equals(currentVersion)) {
      throw new ResourceVersionConflictException("Expected version " + currentVersion);
   }
   
   // ... perform the update ...
   return new MethodOutcome();
   
}

4.4.4Instance Level - Delete

 

The delete operation retrieves a specific version of a resource with a given ID. It takes a single ID parameter annotated with an @IdParam annotation, which supplies the ID of the resource to delete.

@Delete()
public void deletePatient(@IdParam IdType theId) {
   // .. Delete the patient ..
   if (couldntFindThisId) {
      throw new ResourceNotFoundException("Unknown version");
   }
   if (conflictHappened) {
      throw new ResourceVersionConflictException("Couldn't delete because [foo]");
   }
   // otherwise, delete was successful
   return; // can also return MethodOutcome
}

Delete methods are allowed to return the following types:

  • void: This method may return void, in which case the server will return an empty response and the client will ignore any successful response from the server (failure responses will still throw an exception)
  • MethodOutcome: This method may return a MethodOutcome, which is a wrapper for the FHIR OperationOutcome resource, which may optionally be returned by the server according to the FHIR specification.

Example URL to invoke this method (HTTP DELETE): http://fhir.example.com/Patient/111

Conditional Deletes

The FHIR specification also allows "conditional deletes". A conditional delete uses a search style URL instead of a read style URL, and deletes a single resource if it matches the given search parameters. The following example shows how to invoke a conditional delete.

@Delete()
public void deletePatientConditional(@IdParam IdType theId, @ConditionalUrlParam String theConditionalUrl) {
   // Only one of theId or theConditionalUrl will have a value depending
   // on whether the URL received was a logical ID, or a conditional
   // search string
   if (theId != null) {
      // do a normal delete
   } else {
      // do a conditional delete
   }
   
   // otherwise, delete was successful
   return; // can also return MethodOutcome
}

Example URL to perform a conditional delete (HTTP DELETE): http://fhir.example.com/Patient?identifier=system%7C0001

4.4.5Instance Level - Patch

 

HAPI FHIR includes basic support for the patch operation. This support allows you to perform patches, but does not include logic to actually implement resource patching in the server framework (note that the JPA server does include a patch implementation).

The following snippet shows how to define a patch method on a server:

@Patch public OperationOutcome patientPatch(@IdParam IdType theId, PatchTypeEnum thePatchType, @ResourceParam String theBody) {

   if (thePatchType == PatchTypeEnum.JSON_PATCH) {
      // do something
   }
   if (thePatchType == PatchTypeEnum.XML_PATCH) {
      // do something
   }
   
   OperationOutcome retVal = new OperationOutcome();
   retVal.getText().setDivAsString("<div>OK</div>");
   return retVal;
}

4.4.6Type Level - Create

 

The create operation saves a new resource to the server, allowing the server to give that resource an ID and version ID.

Create methods must be annotated with the @Create annotation, and have a single parameter annotated with the @ResourceParam annotation. This parameter contains the resource instance to be created. See the @ResourceParam for information on the types allowed for this parameter (resource types, String, byte[]).

Create methods must return an object of type MethodOutcome. This object contains the identity of the created resource.

The following snippet shows how to define a server create method:

@Create public MethodOutcome createPatient(@ResourceParam Patient thePatient) {

  /* 
   * First we might want to do business validation. The UnprocessableEntityException
   * results in an HTTP 422, which is appropriate for business rule failure
   */
  if (thePatient.getIdentifierFirstRep().isEmpty()) {
    /* It is also possible to pass an OperationOutcome resource
     * to the UnprocessableEntityException if you want to return
     * a custom populated OperationOutcome. Otherwise, a simple one
     * is created using the string supplied below. 
     */
    throw new UnprocessableEntityException("No identifier supplied");
  }
   
  // Save this patient to the database...
  savePatientToDatabase(thePatient);

  // This method returns a MethodOutcome object which contains
  // the ID (composed of the type Patient, the logical ID 3746, and the
  // version ID 1)
  MethodOutcome retVal = new MethodOutcome();
  retVal.setId(new IdType("Patient", "3746", "1"));
  
  // You can also add an OperationOutcome resource to return
  // This part is optional though:
  OperationOutcome outcome = new OperationOutcome();
  outcome.addIssue().setDiagnostics("One minor issue detected");
  retVal.setOperationOutcome(outcome);  
  
  return retVal;
}

Example URL to invoke this method (this would be invoked using an HTTP POST, with the resource in the POST body): http://fhir.example.com/Patient

The following snippet shows how the corresponding client interface would look:

@Create public abstract MethodOutcome createNewPatient(@ResourceParam Patient thePatient);

Conditional Creates

The FHIR specification also allows "conditional creates". A conditional create has an additional header called If-None-Exist which the client will supply on the HTTP request. The client will populate this header with a search URL such as Patient?identifier=foo. See the FHIR specification for details on the semantics for correctly implementing conditional create.

When a conditional create is detected (i.e. when the create request contains a populated If-None-Exist header), if a method parameter annotated with the @ConditionalUrlParam is detected, it will be populated with the value of this header.

@Create public MethodOutcome createPatientConditional(
      @ResourceParam Patient thePatient,
      @ConditionalUrlParam String theConditionalUrl) {

   if (theConditionalUrl != null) {
      // We are doing a conditional create

      // populate this with the ID of the existing resource which 
      // matches the conditional URL
      return new MethodOutcome();  
   } else {
      // We are doing a normal create
      
      // populate this with the ID of the newly created resource
      return new MethodOutcome();  
   }
   
}

Example HTTP transaction to perform a conditional create:


{ ...resource body... }

Prefer Header / Returning the resource body

If you wish to allow your server to honour the Prefer header, the same mechanism shown above for Prefer Header for Updates should be used.

Accessing The Raw Resource Payload

The create operation also supports access to the raw payload, using the same semantics as raw payload access for the update operation.

4.4.7Type Level - Search

 

The search operation returns a bundle with zero-to-many resources of a given type, matching a given set of parameters.

Searching is a very powerful and potentially very complicated operation to implement, with many possible parameters and combinations of parameters. See REST Operations: Search for details on how to create search methods.

4.4.8Type Level - Validate

 

The validate operation tests whether a resource passes business validation, and would be acceptable for saving to a server (e.g. by a create or update method).

Validate methods must be annotated with the @Validate annotation, and have a parameter annotated with the @ResourceParam annotation. This parameter contains the resource instance to be created.

Validate methods may optionally also have a parameter of type IdType annotated with the @IdParam annotation. This parameter contains the resource ID (see the FHIR specification for details on how this is used).

Validate methods must return normally if the resource validates successfully, or throw an UnprocessableEntityException or InvalidRequestException if the validation fails.

Validate methods must return either:

  • void – The method should throw an exception for a validation failure, or return normally.

  • An object of type MethodOutcome. The MethodOutcome may optionally be populated with an OperationOutcome resource, which will be returned to the client if it exists.

The following snippet shows how to define a server validate method:

@Validate public MethodOutcome validatePatient(@ResourceParam Patient thePatient, 
                                     @Validate.Mode ValidationModeEnum theMode,
                                     @Validate.Profile String theProfile) {

  // Actually do our validation: The UnprocessableEntityException
  // results in an HTTP 422, which is appropriate for business rule failure
  if (thePatient.getIdentifierFirstRep().isEmpty()) {
    /* It is also possible to pass an OperationOutcome resource
     * to the UnprocessableEntityException if you want to return
     * a custom populated OperationOutcome. Otherwise, a simple one
     * is created using the string supplied below. 
     */
    throw new UnprocessableEntityException("No identifier supplied");
  }
   
  // This method returns a MethodOutcome object
  MethodOutcome retVal = new MethodOutcome();

  // You may also add an OperationOutcome resource to return
  // This part is optional though:
  OperationOutcome outcome = new OperationOutcome();
  outcome.addIssue().setSeverity(IssueSeverity.WARNING).setDiagnostics("One minor issue detected");
  retVal.setOperationOutcome(outcome);  

  return retVal;
}

In the example above, only the @ResourceParam parameter is technically required, but you may also add the following parameters:

  • @Validate.Mode ValidationModeEnum theMode - This is the validation mode (see the FHIR specification for information on this)

  • @Validate.Profile String profile - This is the profile to validate against (see the FHIR specification for more information on this)

Example URL to invoke this method (this would be invoked using an HTTP POST, with a Parameters resource in the POST body): http://fhir.example.com/Patient/$validate

4.4.9System Level - Capabilities

 

FHIR defines that a FHIR Server must be able to export a Capability Statement (formerly called a Conformance Statement), which is an instance of the CapabilityStatement resource describing the server itself.

The HAPI FHIR RESTful server will automatically export such a capability statement. See the Server Capability Statement documentation for more information.

If you wish to override this default behaviour by creating your own capability statement provider, you simply need to define a class with a method annotated using the @Metadata annotation.

An example provider is shown below.


  @Metadata
  public CapabilityStatement getServerMetadata() {
     CapabilityStatement retVal = new CapabilityStatement();
    // ..populate..
    return retVal;
  }

}

To create a Client which can retrieve a Server's conformance statement is simple. First, define your Client Interface, using the @Metadata annotation:

  
  @Metadata
  CapabilityStatement getServerMetadata();
  
  // ....Other methods can also be added as usual....
  
}

You can then use the standard Annotation Client mechanism for instantiating a client:

MetadataClient client = ctx.newRestfulClient(MetadataClient.class, "http://spark.furore.com/fhir");
CapabilityStatement metadata = client.getServerMetadata();
System.out.println(ctx.newXmlParser().encodeResourceToString(metadata));

4.4.10System Level - Transaction

 

The transaction action is among the most challenging parts of the FHIR specification to implement. It allows the user to submit a bundle containing a number of resources to be created/updated/deleted as a single atomic transaction.

HAPI provides a skeleton for implementing this action, although most of the effort will depend on the underlying implementation. The following example shows how to define a transaction method.

@Transaction public Bundle transaction(@TransactionParam Bundle theInput) {
   for (BundleEntryComponent nextEntry : theInput.getEntry()) {
      // Process entry
   }

   Bundle retVal = new Bundle();
   // Populate return bundle
   return retVal;
}

Transaction methods require one parameter annotated with @TransactionParam, and that parameter may be of type List<IBaseResource> or Bundle`.

In terms of actually implementing the method, unfortunately there is only so much help HAPI will give you. One might expect HAPI to automatically delegate the individual operations in the transaction to other methods on the server but at this point it does not do that. There is a lot that transaction needs to handle (making everything atomic, replacing placeholder IDs across multiple resources which may even be circular, handling operations in the right order) and so far we have not found a way for the framework to do this in a generic way.

What it comes down to is the fact that transaction is a tricky thing to implement. For what it's worth, you could look at the HAPI FHIR JPA Server TransactionProcessor class for inspiration on how to build a transaction processor of your own (note that this class is tightly coupled with the rest of the JPA Server so it is unlikely that it can be used directly outside of that context).

Example URL to invoke this method (note that the URL is the base URL for the server, and the request body is a Bundle resource):

Content-Type: application/fhir+json

{
   "resourceType": "Bundle",
   "type": "transaction",
   "entry": [ ...entries... ]
}

4.4.11System Level - Search

 

Not yet implemented - Get in touch if you would like to help!

4.4.12History (Instance, Type, Server)

 

The history operation retrieves a historical collection of all versions of a single resource (instance history), all resources of a given type (type history), or all resources of any type on a server (server history).

History methods are annotated with the @History annotation, and will have additional requirements depending on the kind of history method intended:

  • For an Instance History method, the method must have a parameter annotated with the @IdParam annotation, indicating the ID of the resource for which to return history. The method must either be defined in a resource provider, or must have a type() value in the @History annotation if it is defined in a plain provider.

  • For a Type History method, the method must not have any @IdParam parameter. The method must either be defined in a resource provider, or must have a type() value in the @History annotation if it is defined in a plain provider.

  • For a Server History method, the method must not have any @IdParam parameter, and must not have a type() value specified in the @History annotation. The method must be defined in a plain provider.

The following snippet shows how to define a history method on a server. Note that the following parameters are both optional, but may be useful in implementing the history operation:

@History()
public List<Patient> getPatientHistory(
      @IdParam IdType theId,
      @Since InstantType theSince,
      @At DateRangeParam theAt
      ) {
   List<Patient> retVal = new ArrayList<Patient>();
   
   Patient patient = new Patient();
   patient.addName().setFamily("Smith");
   
   // Set the ID and version
   patient.setId(theId.withVersion("1"));
   
   // ...populate the rest...
   return retVal;
}

The following snippet shows how to define various history methods in a client.

  /** Server level (history of ALL resources) */ 
  @History
  Bundle getHistoryServer();

  /** Type level (history of all resources of a given type) */
  @History(type=Patient.class)
  Bundle getHistoryPatientType();

  /** Instance level (history of a specific resource instance by type and ID) */
  @History(type=Patient.class)
  Bundle getHistoryPatientInstance(@IdParam IdType theId);

  /**
   * Either (or both) of the "since" and "count" parameters can
   * also be included in any of the methods above.
   */
  @History
  Bundle getHistoryServerWithCriteria(@Since Date theDate, @Count Integer theCount);

}

4.4.13Exceptions

 

When implementing a server operation, there are a number of failure conditions specified. For example, an Instance Read request might specify an unknown resource ID, or a Type Create request might contain an invalid resource which can not be created.

See REST Exception Handling for information on available exceptions.

4.4.14Tags

 

FHIR RESTful servers may support a feature known as tagging. Tags are a set of named flags called which use a FHIR Coding datatype (meaning that they have a system, value, and display just like any other coded field).

Tags have very specific semantics, which may not be obvious simply by using the HAPI API. It is important to review the specification Tags Documentation before attempting to implement tagging in your own applications.

Accessing Tags in a Read / VRead / Search Method

Tags are stored within a resource object, in the Resource.meta element. It is important to note that changing a resource's tags will not cause a version update to that resource.

In a server implementation, you may populate your tags into the returned resource(s) and HAPI will automatically place these tags into the response headers (for read/vread) or the bundle category tags (for search). The following example illustrates how to return tags from a server method. This example shows how to supply tags in a read method, but the same approach applies to vread and search operations as well.

@Read()
public Patient readPatient(@IdParam IdType theId) {
  Patient retVal = new Patient();
 
  // ..populate demographics, contact, or anything else you usually would..

  // Populate some tags
  retVal.getMeta().addTag("http://animals", "Dog", "Canine Patient"); // TODO: more realistic example
  retVal.getMeta().addTag("http://personality", "Friendly", "Friendly"); // TODO: more realistic example
  
  return retVal;
}

In a client operation, you simply call the read/vread/search method as you normally would (as described above), and if any tags have been returned by the server, these may be accessed from the resource metadata.

Patient patient = client.readPatient(new IdType("1234"));
  
// Access the tag list List<Coding> tagList = patient.getMeta().getTag();
for (Coding next : tagList) {
  // ..process the tags somehow..
}

Setting Tags in a Create/Update Method

Within a Type Create or Instance Update method, it is possible for the client to specify a set of tags to be stored along with the saved resource instance.

Note that FHIR specifies that in an update method, any tags supplied by the client are copied to the newly saved version, as well as any tags the existing version had.

To work with tags in a create/update method, the pattern used in the read examples above is simply reversed. In a server, the resource which is passed in will be populated with any tags that the client supplied:

@Create public MethodOutcome createPatientResource(@ResourceParam Patient thePatient) {

  // ..save the resource..
  IdType id = new IdType("123"); // the new database primary key for this resource

  // Get the tag list
  List<Coding> tags = thePatient.getMeta().getTag();
  for (Coding tag : tags) {
    // process/save each tag somehow   
  }
  
  return new MethodOutcome(id);
}

Removing Tags

In order to remove a tag, it does not suffice to remove it from the resource. Tags can be removed using the Resource Operation Meta Delete, which takes a Parameter definining which tags to delete.

4.4.15Handling _summary and _elements

 

The _summary and _elements parameters are automatically handled by the server, so no coding is required to make this work.

However, if you wish to add parameters to manually handle these fields, the following example shows how to access these. This can be useful if you have an architecture where it is more work for the database/storage engine to load all fields.

@Search public List<Patient> search(
   SummaryEnum theSummary, // will receive the summary (no annotation required)
   @Elements Set<String> theElements // (requires the @Elements annotation)
      ) {
   return null; // todo: populate
}

4.4.16Compartments

 

FHIR defines a mechanism for logically grouping resources together called compartments.

To define a search by compartment, you simply need to add the compartmentName() attribute to the @Search annotation, and add an @IdParam parameter.

The following example shows a search method in a resource provider which returns a compartment. Note that you may also add @RequiredParam and @OptionalParam parameters to your compartment search method.

   
   @Override
   public Class<? extends IBaseResource> getResourceType() {
      return Patient.class;
   }
   
   @Search(compartmentName="Condition")
   public List<IBaseResource> searchCompartment(@IdParam IdType thePatientId) {
      List<IBaseResource> retVal=new ArrayList<IBaseResource>(); 
      
      // populate this with resources of any type that are a part of the
      // "Condition" compartment for the Patient with ID "thePatientId"
      
      return retVal;
   }

   // .. also include other Patient operations ..
}

Example URL to invoke this method: http://fhir.example.com/Patient/123/Condition