This page shows the operations which can be implemented on HAPI RESTful Servers, as well as Annotation Clients. 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.

Instance Level - Read

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

@Read()
public Patient getResourceById(@IdParam IdDt theId) {
   Patient retVal = new Patient();
   
   // ...populate...
   retVal.addIdentifier().setSystem("urn:mrns").setValue("12345");
   retVal.addName().addFamily("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 IdDt("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 IdDt theId);

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

Instance 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 IdDt may or may not have the version populated depending on the client request.

@Read(version=true)
public Patient readOrVread(@IdParam IdDt 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

Instance 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:

@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 IdDt theId, @ResourceParam Patient thePatient);

Conditional Updates

If you wish to suport conditional updates, you can add a parameter tagged with a @ConditionalOperationParam 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 IdDt 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 additonal data elements.

@Update
public MethodOutcome updatePatientWithRawValue (
    @ResourceParam Patient thePatient, 
    @IdParam IdDt 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 IdDt theId) {

   // Save the patient to the database
   
   // Update the version and last updated time on the resource
   IdDt updatedId = theId.withVersion("123");
   thePatient.setId(updatedId);
   InstantDt lastUpdated = InstantDt.withCurrentTime();
   ResourceMetadataKeyEnum.UPDATED.put(thePatient, 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 "conention 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,

PUT [serverBase]/Patient/123
If-Match: W/"3"

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();
   
}

Instance 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 IdDt 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 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

@Delete()
public void deletePatientConditional(@IdParam IdDt theId, @ConditionalUrlParam String theConditionalUrl) {
   // Only one of theId or theConditionalUrl will have a value depending
   // on whether the URL receieved 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

Type 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 IdDt("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 @ConditionalOperationParam 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 URL and HTTP header to perform a conditional create:
http://fhir.example.com/Patient
If-None-Exist: Patient?identifier=system%7C0001

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.

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

Search with No Parameters

The following example shows a search with no parameters. This operation should return all resources of a given type (this obviously doesn't make sense in all contexts, but does for some resource types).

@Search
public List<Organization> getAllOrganizations() {
   List<Organization> retVal=new ArrayList<Organization>(); // populate this
   return retVal;
}

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

Search Parameters: String Introduction

To allow a search using given search parameters, add one or more parameters to your search method and tag these parameters as either @RequiredParam or @OptionalParam .

This annotation takes a "name" parameter which specifies the parameter's name (as it will appear in the search URL). FHIR defines standardized parameter names for each resource, and these are available as constants on the individual HAPI resource classes.

Parameters which take a string as their format should use the StringParam type. They may also use normal java String, although it is not possible to use the :exact qualifier in that case.

@Search()
public List<Patient> searchByLastName(@RequiredParam(name=Patient.SP_FAMILY) StringParam theFamily) {
   String valueToMatch = theFamily.getValue();
   
   if (theFamily.isExact()) {
	   // Do an exact match search
   } else {
	   // Do a fuzzy search if possible
   }
   
   // ...populate...
   Patient patient = new Patient();
   patient.addIdentifier().setSystem("urn:mrns").setValue("12345");
   patient.addName().addFamily("Smith").addGiven("Tester").addGiven("Q");
   // ...etc...

   //  Every returned resource must have its logical ID set. If the server
   //  supports versioning, that should be set too
   String logicalId = "4325";
   String versionId = "2"; // optional
   patient.setId(new IdDt("Patient", logicalId, versionId));
   
   /*
    * This is obviously a fairly contrived example since we are always
    * just returning the same hardcoded patient, but in a real scenario
    * you could return as many resources as you wanted, and they
    * should actually match the given search criteria.
    */
   List<Patient> retVal = new ArrayList<Patient>();
   retVal.add(patient);
   
   return retVal;
}

Example URL to invoke this method:
http://fhir.example.com/Patient?family=SMITH

Search Parameters: Token/Identifier

The "token" type is used for parameters which have two parts, such as an idnetifier (which has a system URI, as well as the actual identifier) or a code (which has a code system, as well as the actual code). For example, the search below can be used to search by identifier (e.g. search for an MRN).

@Search()
public List<Patient> searchByIdentifier(@RequiredParam(name=Patient.SP_IDENTIFIER) TokenParam theId) {
   String identifierSystem = theId.getSystem();
   String identifier = theId.getValue();
   
   List<Patient> retVal = new ArrayList<Patient>();
   // ...populate...
   return retVal;
}

Example URL to invoke this method:
http://fhir.example.com/Patient?identifier=urn:foo|7000135

Search Parameters: Date (Simple)

The FHIR specification provides a sytax for specifying dates+times (but for simplicity we will just say dates here) as search criteria.

Dates may be optionally prefixed with a qualifier. For example, the string >=2011-01-02 means any date on or after 2011-01-02.

To accept a qualified date parameter, use the DateParam parameter type.

@Search()
public List<Patient> searchByObservationNames( @RequiredParam(name=Patient.SP_BIRTHDATE) DateParam theDate ) {
   ParamPrefixEnum prefix = theDate.getPrefix(); // e.g. gt, le, etc..
   Date date = theDate.getValue(); // e.g. 2011-01-02
   TemporalPrecisionEnum precision = theDate.getPrecision(); // e.g. DAY
	
   List<Patient> retVal = new ArrayList<Patient>();
   // ...populate...
   return retVal;
}

Example URL to invoke this method:
http://fhir.example.com/Observation?birthdate=>=2011-01-02

Invoking a client of thie type involves the following syntax:

DateParam param = new DateParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, "2011-01-02");
List<Patient> response = client.getPatientByDob(param);

Search Parameters: Date (Ranges)

A common scenario in searches is to allow searching for resources with values (i.e timestamps) within a range of dates.

FHIR allows for multiple parameters with the same key, and interprets these as being an "AND" set. So, for example, a range of date=>=2011-01-01&date=<2011-02-01
can be interpreted as any date within January 2011.

The following snippet shows how to accept such a range, and combines it with a specific identifier, which is a common scenario. (i.e. Give me a list of observations for a specific patient within a given date range)

@Search()
public List<Observation> searchByDateRange(
    @RequiredParam(name=Observation.SP_DATE) DateRangeParam theRange ) {
  
  Date from = theRange.getLowerBoundAsInstant();
  Date to = theRange.getUpperBoundAsInstant();
	
  List<Observation> retVal = new ArrayList<Observation>();
  // ...populate...
  return retVal;
}

Example URL to invoke this method:
http://fhir.example.com/Observation?subject.identifier=7000135&date=>=2011-01-01&date=<2011-02-01

Invoking a client of this type involves the following syntax:

DateParam param = new DateParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, "2011-01-02");
List<Patient> response = client.getPatientByDob(param);

Unbounded Ranges

Note that when using a date range parameter, it is also possible for the client to request an "unbounded" range. In other words, a range that only a start date and no end date, or vice versa.

An example of this might be the following URL, which refers to any Observation resources for the given MRN and having a date after 2011-01-01.
http://fhir.example.com/Observation?subject.identifier=7000135&date=>=2011-01-01
When such a request is made of a server (or to make such a request from a client), the getLowerBound() or getUpperBound() property of the DateRangeParam object will be set to null .

Search Parameters: Quantity

Quantity parameters allow a number with units and a comparator

The following snippet shows how to accept such a range, and combines it with a specific identifier, which is a common scenario. (i.e. Give me a list of observations for a specific patient within a given date range)

@Search()
public List<Observation> getObservationsByQuantity(
        @RequiredParam(name=Observation.SP_VALUE_QUANTITY) QuantityParam theQuantity) {
  
  List<Observation> retVal = new ArrayList<Observation>();
  
  ParamPrefixEnum prefix = theQuantity.getPrefix();
  BigDecimal value = theQuantity.getValue();
  String units = theQuantity.getUnits();
  // .. Apply these parameters ..
  
  // ... populate ...
  return retVal;
}

Example URL to invoke this method:
http://fhir.example.com/Observation?value-quantity=<=123.2||mg

Search Parameters: Resource Reference

Many search parameters refer to resource references. For instance, the Patient parameter "provider" refers to the resource marked as the managing organization for patients.

Reference parameters use the ReferenceParam type. Reference parameters are, in their most basic form, just a pointer to another resource. For example, you might want to query for DiagnosticReport resources where the subject (the Patient resource that the report is about) is Patient/123. The following example shows a simple resource reference parameter in use.

@Search
public List<Patient> findPatientsWithSimpleReference(
		@OptionalParam(name=Patient.SP_CAREPROVIDER) ReferenceParam theProvider
		) {
   List<Patient> retVal=new ArrayList<Patient>();

   // If the parameter passed in includes a resource type (e.g. ?provider:Patient=123)
   // that resoruce type is available. Here we just check that it is either not provided
   // or set to "Patient"
   if (theProvider.hasResourceType()) {
      String resourceType = theProvider.getResourceType();
      if ("Patient".equals(resourceType) == false) {
         throw new InvalidRequestException("Invalid resource type for parameter 'provider': " + resourceType);
      }
   }
   
   if (theProvider != null) {
      // ReferenceParam extends IdDt so all of the resource ID methods are available
      String providerId = theProvider.getIdPart();
      
      // .. populate retVal will Patient resources having provider with id "providerId" ..
      
   }
   
   return retVal;

}

Chained Resource References

References may also support a "chained" value. This is a search parameter name on the target resource. For example, you might want to search for DiagnosticReport resources by subject, but use the subject's last name instead of their resource ID. In this example, you are chaining "family" (the last name) to "subject" (the patient). The net result in the query string would look like:
http://fhir.example.com/DiagnosticReport?subject.family=SMITH
What this query says is "fetch me all of the DiagnosticReport resources where the subject (Patient) of the report has the family (name) of 'SMITH'".

There are two ways of dealing with chained parameters in your methods: static chains and dynamic chains. Both are equally valid, although dyamic chains might lead to somewhat more compact and readable code.

Dynamic Chains

Chained values must be explicitly declared through the use of a whitelist (or blacklist). The following example shows how to declare a report with an allowable chained parameter:

@Search
public List<DiagnosticReport> findReportsWithChain(
    @RequiredParam(name=DiagnosticReport.SP_SUBJECT, chainWhitelist= {Patient.SP_FAMILY, Patient.SP_GENDER}) ReferenceParam theSubject
    ) {
   List<DiagnosticReport> retVal=new ArrayList<DiagnosticReport>();

   String chain = theSubject.getChain();
   if (Patient.SP_FAMILY.equals(chain)) {
      String familyName = theSubject.getValue();
      // .. populate with reports matching subject family name ..
   }
   if (Patient.SP_GENDER.equals(chain)) {
      String gender = theSubject.getValue();
      // .. populate with reports matching subject gender ..
   }

   return retVal;
}

You may also specify the whitelist value of "" to allow an empty chain (e.g. ther resource ID) and this can be combined with other values, as shown below:

@Search
public List<DiagnosticReport> findReportsWithChainCombo (
  @RequiredParam(name=DiagnosticReport.SP_SUBJECT, chainWhitelist= {"", Patient.SP_FAMILY}) ReferenceParam theSubject
  ) {
   List<DiagnosticReport> retVal=new ArrayList<DiagnosticReport>();

   String chain = theSubject.getChain();
   if (Patient.SP_FAMILY.equals(chain)) {
      String familyName = theSubject.getValue();
      // .. populate with reports matching subject family name ..
   }
   if ("".equals(chain)) {
      String resourceId = theSubject.getValue();
      // .. populate with reports matching subject with resource ID ..
   }

   return retVal;
}

If you are handling multiple types of chained parameters in a single method, you may want to convert the reference parameter type into something more convenient before using its value. The following example shows how to do that.

@Search()
public List<Observation> findBySubject(
      @RequiredParam(name=Observation.SP_SUBJECT, chainWhitelist = {"", Patient.SP_IDENTIFIER, Patient.SP_BIRTHDATE}) ReferenceParam subject
      ) {
    List<Observation> observations = new ArrayList<Observation>();

    String chain = subject.getChain();
    if (Patient.SP_IDENTIFIER.equals(chain)) {

       // Because the chained parameter "subject.identifier" is actually of type
       // "token", we convert the value to a token before processing it. 
       TokenParam tokenSubject = subject.toTokenParam(myContext);
       String system = tokenSubject.getSystem();
       String identifier = tokenSubject.getValue();
       
       // TODO: populate all the observations for the identifier
       
    } else if (Patient.SP_BIRTHDATE.equals(chain)) {

          // Because the chained parameter "subject.birthdate" is actually of type
          // "date", we convert the value to a date before processing it. 
          DateParam dateSubject = subject.toDateParam(myContext);
          DateTimeDt birthDate = dateSubject.getValueAsDateTimeDt();
          
          // TODO: populate all the observations for the birthdate
          
    } else if ("".equals(chain)) {
        
       String resourceId = subject.getValue();
        // TODO: populate all the observations for the resource id
        
    }

    return observations;
}

Static Chains

It is also possible to explicitly state a chained value right in the parameter name. This is useful if you want to only support a search by a specific given chained parameter. It has the added bonus that you can use the correct parameter type of the chained parameter (in this case a TokenParameter because the Patient.identifier parameter is a token)

@Search
public List<Patient> findObservations(
		@RequiredParam(name=Observation.SP_SUBJECT+'.'+Patient.SP_IDENTIFIER) TokenParam theProvider
		) {

  String system = theProvider.getSystem();
  String identifier = theProvider.getValue();

  // ...Do a search for all observations for the given subject...
  
  List<Patient> retVal=new ArrayList<Patient>(); // populate this
  return retVal;

}

Search Parameters: Composite

Composite search parameters incorporate two parameters in a single value. Each of those parameters will themselves have a parameter type.

In the following example, Observation.name-value-date is shown. This parameter is a composite of a string and a date. Note that the composite parameter types (StringParam and DateParam) must be specified in both the annotation's compositeTypes field, as well as the generic types for the CompositeParam method parameter itself.

@Search()
public List<Observation> searchByComposite(
		@RequiredParam(name=Observation.SP_CODE_VALUE_DATE, compositeTypes= {TokenParam.class, DateParam.class}) 
		CompositeParam<TokenParam, DateParam> theParam) {
  // Each of the two values in the composite param are accessible separately.
  // In the case of Observation's name-value-date, the left is a string and
  // the right is a date.
  TokenParam observationName = theParam.getLeftValue();
  DateParam observationValue = theParam.getRightValue();
	
  List<Observation> retVal = new ArrayList<Observation>();
  // ...populate...
  return retVal;
}

Example URL to invoke this method:
http://fhir.example.com/Observation?name-value-date=PROCTIME$2001-02-02

Combining Multiple Parameters

Search methods may take multiple parameters, and these parameters may (or may not) be optional. To add a second required parameter, annotate the parameter with @RequiredParam . To add an optional parameter (which will be passed in as null if no value is supplied), annotate the method with @OptionalParam .

You may annotate a method with any combination of as many @RequiredParam and as many @OptionalParam parameters as you want. It is valid to have only @RequiredParam parameters, or only @OptionalParam parameters, or any combination of the two.

If you wish to create a server that can accept any combination of a large number of parameters, (this is how the various reference servers behave, as well as the public HAPI server) the easiest way to accomplish this is to simply create one method with all allowable parameters, each annotated as @OptionalParam.

On the other hand, if you have specific combinations of parameters you wish to support (a common scenario if you are building FHIR on top of existing data sources and only have certain indexes you can use) you could create multiple search methods, each with specific required and optional parameters matching the database indexes.

The following example shows a method with two parameters.

@Search()
public List<Patient> searchByNames( @RequiredParam(name=Patient.SP_FAMILY) StringParam theFamilyName,
                                    @OptionalParam(name=Patient.SP_GIVEN)  StringParam theGivenName ) {
   String familyName = theFamilyName.getValue();
   String givenName = theGivenName != null ? theGivenName.getValue() : null;
   
   List<Patient> retVal = new ArrayList<Patient>();
   // ...populate...
   return retVal;
}

Example URLs to invoke this method:
http://fhir.example.com/Patient?family=SMITH
http://fhir.example.com/Patient?family=SMITH&given=JOHN

Multi-Valued (AND/OR) Parameters

It is possible to accept multiple values of a single parameter as well. This is useful in cases when you want to return a list of resources with criteria matching a list of possible values. See the FHIR Specification for more information.

The FHIR specification allows two types of composite parameters:

  • Where a parameter may accept multiple comma separated values within a single value string (e.g. ?language=FR,NL ) this is treated as an OR relationship, and the search should return elements matching either one or the other.
  • Where a parameter may accept multiple value strings for the same parameter name (e.g. ?language=FR&language=NL ) this is treated as an AND relationship, and the search should return only elements matching both.

It is worth noting that according to the FHIR specification, you can have an AND relationship combining multiple OR relationships, but not vice-versa. In other words, it's possible to support a search like ("name" = ("joe" or "john")) AND ("age" = (11 or 12)) but not a search like ("language" = ("en" AND "fr") OR ("address" = ("Canada" AND "Quebec"))

OR Relationship Query Parameters

To accept a composite parameter, use a parameter type which implements the IQueryParameterOr interface.

Each parameter type (StringParam, TokenParam, etc.) has a corresponding parameter which accepts an OR list of parameters. These types are called "[type]OrListParam", for example: StringOrListParam and TokenOrListParam.

The following example shows a search for Observation by name, where a list of names may be passed in (and the expectation is that the server will return Observations that match any of these names):

@Search()
public List<Observation> searchByObservationNames( 
		@RequiredParam(name=Observation.SP_CODE) TokenOrListParam theCodings ) {

   // The list here will contain 0..* codings, and any observations which match any of the 
   // given codings should be returned
   List<BaseCodingDt> wantedCodings = theCodings.getListAsCodings();
   
   List<Observation> retVal = new ArrayList<Observation>();
   // ...populate...
   return retVal;
}

Example URL to invoke this method:
http://fhir.example.com/Observation?name=urn:fakenames|123,urn:fakenames|456

AND Relationship Query Parameters

To accept a composite parameter, use a parameter type which implements the IQueryParameterAnd interface (which in turn encapsulates the corresponding IQueryParameterOr types).

An example follows which shows a search for Patients by address, where multiple string lists may be supplied by the client. For example, the client might request that the address match ("Montreal" OR "Sherbrooke") AND ("Quebec" OR "QC") using the following query:
http://fhir.example.com/Patient?address=Montreal,Sherbrooke&address=Quebec,QC

The following code shows how to receive this parameter using a StringAndListParameter, which can handle an AND list of multiple OR lists of strings.

@Search()
public List<Patient> searchByPatientAddress( 
		@RequiredParam(name=Patient.SP_ADDRESS) StringAndListParam theAddressParts ) {

  // StringAndListParam is a container for 0..* StringOrListParam, which is in turn a
  // container for 0..* strings. It is a little bit weird to understand at first, but think of the
  // StringAndListParam to be an AND list with multiple OR lists inside it. So you will need 
  // to return results which match at least one string within every OR list. 
  List<StringOrListParam> wantedCodings = theAddressParts.getValuesAsQueryTokens();
  for (StringOrListParam nextOrList : wantedCodings) {
    List<StringParam> queryTokens = nextOrList.getValuesAsQueryTokens();
	// Only return results that match at least one of the tokens in the list below
    for (StringParam nextString : queryTokens) {
    	   // ....check for match...
    }
  }
  
  List<Patient> retVal = new ArrayList<Patient>();
  // ...populate...
  return retVal;
}

Note that AND parameters join multiple OR parameters together, but the inverse is not true. In other words, it is possible in FHIR to use AND search parameters to specify a search criteria of (A=1 OR A=2) AND (B=1 OR B=2) but it is not possible to specify (A=1 AND B=1) OR (A=2 AND B=2) (aside from in very specific cases where a composite parameter has been specifically defined).

AND Relationship Query Parameters for Dates

Dates are a bit of a special case, since it is a common scenario to want to match a date range (which is really just an AND query on two qualified date parameters). See the section below on date ranges for an example of a DateRangeParameter.

Resource Includes (_include)

FHIR allows clients to request that specific linked resources be included as contained resources, which means that they will be "embedded" in a special container called "contained" within the parent resource.

HAPI allows you to add a parameter for accepting includes if you wish to support them for specific search methods.

@Search()
public List<DiagnosticReport> getDiagnosticReport( 
               @RequiredParam(name=DiagnosticReport.SP_IDENTIFIER) 
               TokenParam theIdentifier,
               
               @IncludeParam(allow= {"DiagnosticReport:subject"}) 
               Set<Include> theIncludes ) {
	
  List<DiagnosticReport> retVal = new ArrayList<DiagnosticReport>();
 
  // Assume this method exists and loads the report from the DB
  DiagnosticReport report = loadSomeDiagnosticReportFromDatabase(theIdentifier);

  // If the client has asked for the subject to be included:
  if (theIncludes.contains(new Include("DiagnosticReport:subject"))) {
	 
    // The resource reference should contain the ID of the patient
    IdDt subjectId = report.getSubject().getReference();
	
    // So load the patient ID and return it
    Patient subject = loadSomePatientFromDatabase(subjectId);
    report.getSubject().setResource(subject);
	
  }
 
  retVal.add(report);
  return retVal;
}

Example URL to invoke this method:
http://fhir.example.com/DiagnosticReport?identifier=7000135&_include=DiagnosticReport.subject

It is also possible to use a String type for the include parameter, which is more convenient if only a single include (or null for none) is all that is required.

@Search()
public List<DiagnosticReport> getDiagnosticReport( 
             @RequiredParam(name=DiagnosticReport.SP_IDENTIFIER) 
             TokenParam theIdentifier,
             
             @IncludeParam(allow= {"DiagnosticReport:subject"}) 
             String theInclude ) {
	
  List<DiagnosticReport> retVal = new ArrayList<DiagnosticReport>();

  // Assume this method exists and loads the report from the DB
  DiagnosticReport report = loadSomeDiagnosticReportFromDatabase(theIdentifier);

  // If the client has asked for the subject to be included:
  if ("DiagnosticReport:subject".equals(theInclude)) {
	 
    // The resource reference should contain the ID of the patient
    IdDt subjectId = report.getSubject().getReference();
	
    // So load the patient ID and return it
    Patient subject = loadSomePatientFromDatabase(subjectId);
    report.getSubject().setResource(subject);
	
  }

  retVal.add(report);
  return retVal;
}

Reverse Resource Includes (_revinclude)

To add support for reverse includes (via the _revinclude parameter), use the same format as with the _include parameter (shown above) but add reverse=true to the @IncludeParam annotation, as shown below.

@Search()
public List<DiagnosticReport> getDiagnosticReport( 
             @RequiredParam(name=DiagnosticReport.SP_IDENTIFIER) 
             TokenParam theIdentifier,
             
             @IncludeParam() 
             Set<Include> theIncludes, 
             
             @IncludeParam(reverse=true)
             Set<Include> theReverseIncludes
         ) {
 
return new ArrayList<DiagnosticReport>(); // populate this
}

Named Queries (_query)

FHIR supports named queries , which may have specific behaviour defined. The following example shows how to create a Search operation with a name.

This operation can only be invoked by explicitly specifying the given query name in the request URL. Note that the query does not need to take any parameters.

@Search(queryName="namedQuery1")
public List<Patient> searchByNamedQuery(@RequiredParam(name="someparam") StringParam theSomeParam) {
 List<Patient> retVal = new ArrayList<Patient>();
 // ...populate...
 return retVal;
}

Example URL to invoke this method:
http://fhir.example.com/Patient?_query=namedQuery1&someparam=value

Sorting (_sort)

FHIR supports sorting according to a specific set of rules.

According to the specification, sorting is requested by the client using a search param as the sort key. For example, when searching Patient resources, a sort key of "given" requests the "given" search param as the sort key. That param maps to the values in the field "Patient.name.given".

Sort specifications can be passed into handler methods by adding a parameter of type SortSpec, which has been annotated with the @Sort annotation, as shown in the following example:

@Search
public List<Patient> findPatients(
		@RequiredParam(name=Patient.SP_IDENTIFIER) StringParam theParameter,
		@Sort SortSpec theSort) {
   List<Patient> retVal=new ArrayList<Patient>(); // populate this

   // theSort is null unless a _sort parameter is actually provided 
   if (theSort != null) {
      
      // The name of the param to sort by
      String param = theSort.getParamName();

      // The sort order, or null
      SortOrderEnum order = theSort.getOrder();

      // This will be populated if a second _sort was specified
      SortSpec subSort = theSort.getChain();
      
      // ...apply the sort...
   }

   return retVal;
}

Example URL to invoke this method:
http://fhir.example.com/Patient?_identifier=urn:foo|123&_sort=given

Adding Descriptions

It is also possible to annotate search methods and/or parameters with the @Description annotation. This annotation allows you to add a description of the method and the individual parameters. These descriptions will be placed in the server's conformance statement, which can be helpful to anyone who is developing software against your server.

@Description(shortDefinition="This search finds all patient resources matching a given name combination")
@Search()
public List<Patient> searchWithDocs( 
          @Description(shortDefinition="This is the patient's last name - Supports partial matches")
          @RequiredParam(name=Patient.SP_FAMILY) StringParam theFamilyName,

          @Description(shortDefinition="This is the patient's given names")
          @OptionalParam(name=Patient.SP_GIVEN)  StringParam theGivenName ) {
	
  List<Patient> retVal = new ArrayList<Patient>();
  // ...populate...
  return retVal;
}

Type 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).

Note on FHIR versions: In FHIR DSTU1 the validate operation used a URL resembling http://example.com/Patient/_validate with a resource in the HTTP POST body. In FHIR DSTU2, validate has been changed to use the extended operation mechanism. It now uses a URL resembling http://example.com/Patient/$validate and takes a Parameters resource as input in the method body.

The mechanism described below may be used for both DSTU1 and DSTU2+ servers, and HAPI will automatically use the correct form depending on what FHIR version the server is configured to use.

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 oftype IdDt 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(IssueSeverityEnum.WARNING).setDiagnostics("One minor issue detected");
  retVal.setOperationOutcome(outcome);  

  return retVal;
}

In the example above, only the @ResourceParam parameter is technically required, but in DSTU2 you are encouraged to also add the following parameters:

  • @Validate.Mode ValidationModeEnum mode - 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

System Level - Conformance

FHIR defines that a FHIR Server must be able to export a conformance statement, which is an instance of the Conformance resource describing the server itself.

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

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

public class ConformanceProvider {

  @Metadata
  public Conformance getServerMetadata() {
    Conformance retVal = new Conformance();
    // ..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:

public interface MetadataClient extends IRestfulClient {
  
  @Metadata
  Conformance getServerMetadata();
  
  // ....Other methods can also be added as usual....
  
}

Then use the standard RESTful Client mechanism for instantiating a client:

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

System 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 (BundleEntry nextEntry : theInput.getEntries()) {
      // 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<IResource> 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 our JPA module's "transaction" method in our source repository to see how we implemented transaction in the JPA server.

Example URL to invoke this method:
POST http://fhir.example.com/
(note that the content of this POST will be a bundle)

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

History (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 must be 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.
    • For a server implementation, the method must either be defined in a resource provider or 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.
    • For a server implementation, the method must either be defined in a resource provider or 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.
    • In a server implementation, 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:

  • The @Since method argument implements the _since parameter and should be of type DateTimeDt or DateTimeType
  • The @At method argument implements the _at parameter and may be of type DateRangeParam, DateTimeDt or DateTimeType
  • @History()
    public List<Patient> getPatientHistory(
          @IdParam IdDt theId,
          @Since InstantDt theSince,
          @At DateRangeParam theAt
          ) {
       List<Patient> retVal = new ArrayList<Patient>();
       
       Patient patient = new Patient();
       patient.addName().addFamily("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.

    public interface HistoryClient extends IBasicClient {
      /** 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 IdDt theId);
    
      /**
       * Either (or both) of the "since" and "count" paramaters can
       * also be included in any of the methods above.
       */
      @History
      Bundle getHistoryServerWithCriteria(@Since Date theDate, @Count Integer theCount);
    
    }
    

    Exceptions

    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.

    In these cases, an appropriate exception should be thrown. The HAPI RESTful API includes a set of exceptions extending BaseServerResponseException which represent specific HTTP failure codes.

    See the Exceptions List for a complete list of these exceptions. Note that these exceptions are all unchecked exceptions, so they do not need to ne explicitly declared in the method signature.

    Tags

    FHIR RESTful servers may support a feature known as tagging. Tags are a set of named flags called "terms" (with an optional accompanying human friendly name called a "label", and an optional namespace called a "scheme").

    Tags have very specific semantics, which may not be obvious simply by using the HAPI API. It is important to review the specification pages here and here 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 IResource.html#getResourceMetadata() map, under the key TAG_LIST.

    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 IdDt theId) {
      Patient retVal = new Patient();
     
      // ..populate demographics, contact, or anything else you usually would..
     
      // Create a TagList and place a complete list of the patient's tags inside
      TagList tags = new TagList();
      tags.addTag("http://animals", "Dog", "Canine Patient"); // TODO: more realistic example
      tags.addTag("http://personality", "Friendly", "Friendly"); // TODO: more realistic example
      
      // The tags are then stored in the Patient resource instance
      retVal.getResourceMetadata().put(ResourceMetadataKeyEnum.TAG_LIST, tags);
     
      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.

    IPatientClient client = FhirContext.forDstu2().newRestfulClient(IPatientClient.class, "http://foo/fhir");
    Patient patient = client.readPatient(new IdDt("1234"));
      
    // Access the tag list
    TagList tagList = (TagList) patient.getResourceMetadata().get(ResourceMetadataKeyEnum.TAG_LIST);
    for (Tag 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 revered. 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 resouce..
      IdDt id = new IdDt("123"); // the new databse primary key for this resource
    
      // Get the tag list
      TagList tags = (TagList) thePatient.getResourceMetadata().get(ResourceMetadataKeyEnum.TAG_LIST);
      for (Tag tag : tags) {
        // process/save each tag somehow	
      }
      
      return new MethodOutcome(id);
    }
    

    More tag methods

    FHIR also provides a number of operations to interact directly with tags. These methods may be used to retrieve lists of tags that are available on the server, or to add or remove tags from resources without interacting directly with those resources.

    On a server these methods may be placed in a plain provider, or in a resource provider in the case of resource type specific methods.

    public class TagMethodProvider 
    {
      /** Return a list of all tags that exist on the server */
      @GetTags
      public TagList getAllTagsOnServer() {
        return new TagList(); // populate this
      }
    
      /** Return a list of all tags that exist on at least one instance
       *  of the given resource type */
      @GetTags(type=Patient.class)
      public TagList getTagsForAllResourcesOfResourceType() {
        return new TagList(); // populate this
      }
    
      /** Return a list of all tags that exist on a specific instance
       *  of the given resource type */
      @GetTags(type=Patient.class)
      public TagList getTagsForResources(@IdParam IdDt theId) {
        return new TagList(); // populate this
      }
    
      /** Return a list of all tags that exist on a specific version
       *  of the given resource type */
      @GetTags(type=Patient.class)
      public TagList getTagsForResourceVersion(@IdParam IdDt theId) {
        return new TagList(); // populate this
      }
    
      /** Add tags to a resource */
      @AddTags(type=Patient.class)
      public void getTagsForResourceVersion(@IdParam IdDt theId, 
                                            @TagListParam TagList theTagList) {
        // add tags
      }
    
      /** Add tags to a resource version */
      @AddTags(type=Patient.class)
      public void addTagsToResourceVersion(@IdParam IdDt theId,
                                           @TagListParam TagList theTagList) {
        // add tags
      }
    
      /** Remove tags from a resource */
      @DeleteTags(type=Patient.class)
      public void deleteTagsFromResourceVersion(@IdParam IdDt theId,
                                                @TagListParam TagList theTagList) {
        // add tags
      }
    
    }
    

    On a client, the methods are defined in the exact same way, except that there is no method body in the client interface.

    _summary and _elements

    The _summary and _elements parameters are automatically handled by the server, so no coding is required to make this work. If you wish to add parameters to manually handle these fields however, the following example shows how to access these.
    @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
    }
    

    Compartments

    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.

    public class PatientRp implements IResourceProvider {
       
       @Override
       public Class<? extends IResource> getResourceType() {
          return Patient.class;
       }
       
       @Search(compartmentName="Condition")
       public List<IResource> searchCompartment(@IdParam IdDt thePatientId) {
          List<IResource> retVal=new ArrayList<IResource>(); 
          
          // 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

    Extended Operations

    FHIR extended operations are a special type of RPC-style invocation you can perform against a FHIR server, type, or resource instance. These invocations take a Parameters resource as input, and return either another Parameters resource or a different resource type.

    To define an operation, a method should be placed in a Resource Provider class if the operation works against a resource type (e.g. Patient) or a resource instance (e.g. Patient/123), or on a Plain Provider if the operation works against the server (i.e. it is global and not resource specific).

    Type-Specific Operations

    To implement a type-specific operation, the method should be annotated with the @Operation tag, and should have an @OperationParam tag for each named parameter that the input Parameters resource may be populated with. The following example shows how to implement the Patient/$everything method, defined in the FHIR specification.

    @Operation(name="$everything", idempotent=true)
    public Bundle patientTypeOperation(
       @OperationParam(name="start") DateDt theStart,
       @OperationParam(name="end") DateDt theEnd) {
       
       Bundle retVal = new Bundle();
       // Populate bundle with matching resources
       return retVal;
    }
    

    Example URL to invoke this operation (HTTP request body is Parameters resource):
    POST http://fhir.example.com/Patient/$everything

    Instance-Specific Operations

    To create an instance-specific operation (an operation which takes the ID of a specific resource instance as a part of its request URL), you can add a parameter annotated with the @IdParam annotation, of type IdDt. The following example show how to implement the Patient/[id]/$everything operation.

    @Operation(name="$everything", idempotent=true)
    public Bundle patientInstanceOperation(
       @IdParam IdDt thePatientId,
       @OperationParam(name="start") DateDt theStart,
       @OperationParam(name="end") DateDt theEnd) {
       
       Bundle retVal = new Bundle();
       // Populate bundle with matching resources
       return retVal;
    }
    

    Example URL to invoke this operation (HTTP request body is Parameters resource):
    http://fhir.example.com/Patient/123/$everything

    Using Search Parameter Types

    FHIR allows operation parameters to be of a Search parameter type (e.g. token) instead of a FHIR datatype (e.g. Coding).

    To use a search parameter type, any of the search parameter types listed in Search may be used. For example, the following is a simple operation method declaration using search parameters:

    @Operation(name="$find-matches", idempotent=true)
    public Parameters findMatchesBasic(
       @OperationParam(name="date") DateParam theDate,
       @OperationParam(name="code") TokenParam theCode) {
       
       Parameters retVal = new Parameters();
       // Populate bundle with matching resources
       return retVal;
    }
    

    Example URL to invoke this operation (HTTP request body is Parameters resource):
    http://fhir.example.com/$find-matches?date=2011-01-02&code=http://system|value

    It is also fine to use collection types for search parameter types if you want to be able to accept multiple values. For example, a List<TokenParam> could be used if you want to allow multiple repetitions of a given token parameter (this is analogous to the "AND" semantics in a search). A TokenOrListParam could be used if you want to allow multiple values within a single repetition, separated by comma (this is analogous to "OR" semantics in a search).

    For example:

    @Operation(name="$find-matches", idempotent=true)
    public Parameters findMatchesAdvanced(
       @OperationParam(name="dateRange") DateRangeParam theDate,
       @OperationParam(name="name") List<StringParam> theName,
       @OperationParam(name="code") TokenAndListParam theEnd) {
       
       Parameters retVal = new Parameters();
       // Populate bundle with matching resources
       return retVal;
    }
    

    Server Operations

    Server operations do not operate on a specific resource type or instance, but rather operate globally on the server itself. The following example show how to implement the $closure operation. Note that the concept parameter in the example has a cardinality of 0..* according to the FHIR specification, so a List<Coding> is used as the parameter type.

    @Operation(name="$closure")
    public ConceptMap closureOperation(
       @OperationParam(name="name") StringDt theStart,
       @OperationParam(name="concept") List<CodingDt> theEnd,
       @OperationParam(name="version") IdDt theVersion) {
       
       ConceptMap retVal = new ConceptMap();
       // Populate bundle with matching resources
       return retVal;
    }
    

    Example URL to invoke this operation (HTTP request body is Parameters resource):
    http://fhir.example.com/$closure

    Returning Multiple OUT Parameters

    In all of the Extended Operation examples above, the return type specified for the operation is a single Resource instance. This is a common pattern in FHIR defined operations. However, it is also possible for an extended operation to be defined with multiple and/or repeating OUT parameters. In this case, you can return a Parameters resource directly.

    Idempotent Operations / Handling HTTP Get

    The FHIR specification notes that if an operation is idempotent (which means roughly that it does not modity any data or state on the server) then it may be invoked with an HTTP GET instead of an HTTP POST.

    If you are implementing an operation which is idempotent, you should mark your operation with idempotent=true, as shown in some of the examples above. The default value for this flag is false, meaning that operations will not support HTTP GET by default.

    Note that the HTTP GET form is only supported if the operation has only primitive parameters (no complex parameters or resource parameters). If a client makes a request containing a complex parameter, the server will respond with an HTTP 405 Method Not Supported.

    Back to top

    Reflow Maven skin by Andrius Velykis.