The FHIR specification defines a special kind of operations that have an RPC-like functionality. These are called "Execute Operations", or simply "Operations" throughout the FHIR specification.
A good introduction to this capability can be found on the Operations Page of the FHIR Specification.
FHIR extended operations are a special type of RPC-style invocation you can perform against a FHIR server, type, or resource instance. These invocations are named using the convention $name
(i.e. the name is prefixed with $) and will generally take a Parameters resource as input and output. There are some cases where the input and/or output will be a different resource type however.
To define an operation, a method should be placed in a Resource Provider class if the operation works against a resource type/instance (e.g. Patient/$everything
), or on a Plain Provider class if the operation works against the server (i.e. it is global and not resource specific).
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://fhir.example.com/Patient/$everything
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 IdType. The following example shows how to implement the Patient/[id]/$everything
operation.
@Operation(name = "$everything", idempotent = true)
public Bundle patientInstanceOperation(
@IdParam IdType 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://fhir.example.com/Patient/123/$everything
Server-level operations do not operate on a specific resource type or instance, but rather operate globally on the server itself. The following example shows how to implement a server-level operation. Note that the concept
parameter in the example has a cardinality of 0..*
, so a ``List
@Operation(name = "$closure")
public ConceptMap closureOperation(
@OperationParam(name = "name") StringDt theStart,
@OperationParam(name = "concept") List<Coding> theEnd,
@OperationParam(name = "version") IdType 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
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 Rest Operations: 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
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;
}
If the string value to be searched contains a space character, you should encode it with a +
sign or with %20
, as in the following examples with an Organization named "Acme Corporation":
http://fhir.example.com/Organization
http://fhir.example.com/Organization
If the string value to be searched contains a literal +
character, you should escape it with %2B
, as in the following example with an Organization named "H+K":
http://fhir.example.com/Organization
Certain strings are automatically escaped when the FHIR server parses URLs:
"|" -> "%7C"
"=>=" -> "=%3E%3D"
"=<=" -> "=%3C%3D"
"=>" -> "=%3E"
"=<" -> "=%3C"
In all of the 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.
The FHIR specification allows for operations to be invoked using an HTTP GET instead of an HTTP POST only if the following two conditions are met:
All parameters have primitive datatype values
The operation is marked as "affectsState = false". Note that early releases of the FHIR specification referred to an operation that did not affect state as "idempotent = true". It was subsequently determined that idempotency was the wrong term for the concept being expressed, but the term does persist in some HAPI FHIR documentation and code.
If you are implementing an operation which should allow HTTP GET, you should mark your operation with
idempotent=true
in the @Operation. 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
.
For some operations you may wish to bypass the HAPI FHIR standard request parsing and/or response generation. In this case you may use the manualRequest = true
and/or manualResponse = true
attributes on the @Operation annotation.
The following example shows an operation that parses the request and generates a response (by echoing back the request).
@Operation(name = "$manualInputAndOutput", manualResponse = true, manualRequest = true)
public void manualInputAndOutput(HttpServletRequest theServletRequest, HttpServletResponse theServletResponse)
throws IOException {
String contentType = theServletRequest.getContentType();
byte[] bytes = IOUtils.toByteArray(theServletRequest.getInputStream());
ourLog.info("Received call with content type {} and {} bytes", contentType, bytes.length);
theServletResponse.setContentType("text/plain");
theServletResponse.getWriter().write("hello");
theServletResponse.getWriter().close();
}