FHIR Storage Processors

 

FHIR Storage processors perform work using a FHIR Storage module.

Resource Operation Processor

 
  • Example URI: smile:persistence/resourceOperationProcessor
  • Description: Performs the operation represented by the input message. E.g. If the Resource Operation was a Patient CREATE, then it would create that patient in the specified Storage Module.
  • Input: ResourceOperationJsonMessage
  • Output: N/A

Example Route

Resource Operations messages posted to the resource-operation-message-in-topic will get processed by the module with id persistence.

<routes xmlns="http://camel.apache.org/schema/spring">
    <route>
        <from uri="kafka:resource-operation-message-in-topic?brokers=localhost:9092"/>
        <to uri="smile:persistence/resourceOperationProcessor"/>
    </route>
</routes>

Transaction/Batch Bundle Processor

 

This processor accepts a FHIR Transaction or a FHIR Batch Bundle as input and submits it to a FHIR Storage module for processing. FHIR Transactions are commonly used as a mechanism for submitting and loading data into a FHIR repository. They are generally the most efficient way of accomplishing this task, especially at large scale.

  • Example URI: smile:persistence/bundleProcessor
  • Input: IBaseBundle – A FHIR Bundle resource containing a FHIR Transaction or Batch request.
  • Output: The processor sets the following exchange variables:
    • bundleProcessorResponseBundle - An IBaseBundle containing the transaction/batch response. This bundle contains details about the outcomes of the individual items in the transaction/batch.
    • bundleProcessorProcessingTime - A Long containing the processing time in milliseconds.
    • bundleProcessorStartTime - A java.util.Date containing the timestamp when processing started.
    • failedEntryBundle - An IBaseBundle containing any entries that failed processing (only present when useBatchOnFinalAttempt=true and some entries fail).

Processor Parameters

The following parameters are available to this processor:

  • partitionId – (optional) Specifies a hard-coded partition ID to use when processing the Bundle. Value should be an integer specifying the ID of the partition to use.
  • retries – (optional) Specifies a number of retries to attempt if the processing fails. A value of retries=2 means that if the initial attempt to process the transaction fails, it will be retried two more times before giving up and throwing an exception. This parameter can be useful when data being submitted is likely to have collisions, meaning concurrent processing is likely to be modifying the same resources. Value should be a positive integer, or 0 (which is the default). When this parameter is used, the following parameters may also be added:
    • retryDelayMin – (optional) Specifies the minimum amount of time to sleep before attempting a retry. Value should be an integer and is specified in milliseconds.
    • retryDelayMax – (optional) Specifies the maximum amount of time to sleep before attempting a retry. If this number is greater than the retryDelayMin value, a random amount of time will be chosen for each retry. This can be helpful when processing data with high numbers of collisions. Value should be an integer and is specified in milliseconds.
    • useBatchOnFinalAttempt – (optional) If the value useBatchOnFinalAttempt=true is specified, a submitted FHIR Transaction bundle will be converted into a FHIR Batch bundle prior to the final retry attempt. This can be useful if you are loading transaction bundles containing unrelated resources (i.e. resources which to not have references to each other within the Bundle) and want to ensure that the failure to process one resource does not prevent the loading of another. Value should be true or false (which is the default).
  • logProgressInterval – (optional) If specified as a positive number, a log entry will be logged to the system log containing information about recent transaction/batch processing.

Example Route: Process from Kafka

Bundles posted to the bundle-in-topic will get processed by the module with id persistence. A log entry will be emitted to the system log after every 100 bundles has been processed.

Authentication parameters for the Kafka consumer (<from...>) are not included here and may be required, but several important parameters are included:

groupId=my-group-id The Consumer Group ID must be specified in order to ensure that topic offsets are preserved between restarts of the system. The exact value can be any string, but it must never change or be reused by other applications.
maxPollRecords=1 This setting indicates to Kafka that messages should be consumed one-by-one. This is important because by default Kafka uses large batches and expects that they will be processed very quickly, which is more appropriate for large numbers of very small payloads which require minimal processing each. This is not typically the case in a FHIR server, so a small poll size is chosen.
allowManualCommit=true
autoCommitEnable=false
These are required in order to use the kafkaManualCommit processor at the end of the flow. This processor ensures that the topic offset (the current position within the queue) is not advanced until the message has been successfully processed.
autoOffsetReset=earliest This setting instructs the Kafka consumer to begin at the very beginning of the topic, consuming messages that were produced (added to the topic) before Smile CDR was started.

Example Route: Guaranteed Delivery

This route receives messages from a Kafka topic, where each message on the topic should contain a FHIR Transaction Bundle or a FHIR Batch Bundle. The messages are routed for transaction/batch processing on the FHIR Storage module names persistence.

This route uses a Kafka manual commit only after the message has been processed successfully, and does not have any error handler. This means that any failing messages will be retried indefinitely until processing the message succeeds.

<route>
	<from uri="kafka:bundle-in-topic?brokers=localhost:9092&amp;groupId=my-group-id&amp;maxPollRecords=1&amp;allowManualCommit=true&amp;autoCommitEnable=false&amp;autoOffsetReset=earliest"/>
	<to uri="smile:persistence/bundleProcessor&amp;logProgressInterval=100"/>
	<to uri="smile:clustermgr/kafkaManualCommit" />
</route>

Example Route: Retry with Dead Letter Queue

The following example shows a processor which retries twice, and delivers the payload to a failure topic (ie. a Dead Letter Queue) if the final processing attempt fails. Messages on the DLQ topic, named dlq-topic, will contain the failing FHIR transaction.

Note that the Kafka manual commit is only invoked after a successful processed message has completed. This means that if a long series of failing messages is encountered and no messages are successfully processed before the Smile CDR process is terminated, the consumer offset will not be incremented and these failing messages may be processed again by a Smile CDR process which is started later.

<route>
	<errorHandler>
		<deadLetterChannel deadLetterUri="kafka:dlq-topic?brokers=localhost:9092">
			<redeliveryPolicy maximumRedeliveries="3" redeliveryDelay="250"/>
		</deadLetterChannel>
	</errorHandler>
	<from uri="kafka:delivery-topic-with-dlq?brokers=localhost:9092&amp;groupId=my-group-id&amp;maxPollRecords=1&amp;allowManualCommit=true&amp;autoCommitEnable=false&amp;autoOffsetReset=earliest"/>
	<to uri="smile:persistence/bundleProcessor?logProgressInterval=5"/>
	<to uri="smile:clustermgr/kafkaManualCommit" />
</route>

Example Route: Retry with Batch Processing Attempt

The following example shows a processor which retries twice, and then attempts the final processing using a FHIR Batch instead of a FHIR Transaction. The batch processing mode is slower than the transaction processing mode, but means that the system will process as many entries as it can. If any entries fail on the final attempt, then a new FHIR Batch Bundle containing the failing entries will be sent to the Kafka dlq-failed-entry-topic topic.

This kind of route can be used in cases where your FHIR transaction bundles contain a large number of unrelated resources, and it is therefore desirable to process as many of them as possible even if one or more of them is unprocessable. This route can result in these transaction bundles being partially processed, with the remaining partial bundle ending up in the Dead Letter Queue.

<route>
	<from uri="kafka:guaranteed-delivery-topic?brokers=localhost:9092&amp;groupId=my-group-id&amp;maxPollRecords=1&amp;allowManualCommit=true&amp;autoCommitEnable=false&amp;autoOffsetReset=earliest"/>
	<to uri="smile:persistence/bundleProcessor?logProgressInterval=5&amp;retries=3&amp;retryDelayMin=50&amp;retryDelayMax=100&amp;useBatchOnFinalAttempt=true"/>
	<choice>
		<when>
			<variable>failedEntryBundle</variable>
			<to uri="kafka:dlq-failed-entry-topic?brokers=localhost:9092" variableSend="failedEntryBundle"/>
		</when>
	</choice>
	<to uri="smile:clustermgr/kafkaManualCommit" />
</route>

FHIR NDJSON Converter

 
  • Example URI: smile:persistence/ndjsonToBundleProcessor
  • Description: This processor converts NDJSON strings into IBaseBundles. The optional parameter type can be used to specify the kind of bundle to create: transaction (default, if none specified), or batch. This bundle can then be passed to a bundleProcessor processor to persist the bundle to the repository. Additionally, if the optional parameter ensureHomogeneousResourceTypes is defined and set to true, NDJSON parsing will fail if resources of different types are encountered in the same NDJSON file.

Example Routes

Explicitly defining the output bundle as a transaction bundle.

<routes xmlns="http://camel.apache.org/schema/spring">
	<route id="my-route">
		<from uri="kafka:bundle-in-topic?brokers=localhost:9092"/>
		<to uri="smile:persistence/ndjsonToBundleProcessor?type=transaction"/>
		<!-- to persist the result of the transformation -->
		<to uri="smile:persistence/bundleProcessor"/>
	</route>
</routes>

Failing processing if NDJSON source has multiple resource types when only one is expected.

<routes xmlns="http://camel.apache.org/schema/spring">
	<route id="my-route">
		<from uri="kafka:bundle-in-topic?brokers=localhost:9092"/>
		<to uri="smile:persistence/ndjsonToBundleProcessor?ensureHomogeneousResourceTypes=true"/>
		<!-- to persist the result of the transformation -->
		<to uri="smile:persistence/bundleProcessor"/>
	</route>
</routes>

Single Resource Processor

 

This processor accepts a single FHIR resource as input and performs a create, update, or delete operation directly on the FHIR Storage module. This provides a more efficient way to process individual resources without having to wrap them in a Bundle or ResourceOperationMessage. Note that this route is not designed to handle Bundle resources which have a type of transaction or batch. To ingest such a bundle, see the Bundle Processor.

  • Example URI: smile:persistence/singleResourceProcessor
  • Input: IBaseResource – A single FHIR resource (e.g., Patient, Observation, etc.)
  • Output: The processor sets the following exchange variables:
    • outcome - IBaseOperationOutcome containing the result of the operation
    • resourceId - String containing the unqualified, versionless ID of the processed resource (e.g., Patient/123) (if applicable)

Processor Parameters

The following parameters are available to this processor:

  • method – (required) Specifies the operation type to perform. Value must be one of:
    • CREATE - Create a new resource. If a user-supplied ID is present in the resource body, it will be ignored.
    • UPDATE - Update an existing resource. If a user-supplied id is missing from the resource body, the operation will fail. The ID must be present.
    • DELETE - Delete an existing resource. If a user-supplied id is missing from the resource body, the operation will fail. The ID must be present.
  • partitionId – (optional) Specifies a partition ID to use when processing the resource. Value should be an integer specifying the ID of the partition to use.

Example Route: Create Patient Resource

The following example shows a processor that receives a FHIR Patient resource from a kafka topic,and creates it in the FHIR Storage module with ID persistence.

<routes xmlns="http://camel.apache.org/schema/spring">
    <route id="create-patient">
		 <from uri="kafka:resource-in-topic?brokers=localhost:9092&amp;groupId=my-group-id"/>
		 <to uri="smile:persistence/singleResourceProcessor?method=CREATE"/>
    </route>
</routes>

Example Route: Update Resource with Partition

This example shows how to update an existing resource in a specific partition. The route receives the resource from a Kafka topic and updates it in partition 1 of the FHIR Storage module.

<routes xmlns="http://camel.apache.org/schema/spring">
    <route id="update-resource">
        <from uri="kafka:resource-update-topic?brokers=localhost:9092&amp;groupId=resource-update-group"/>
        <to uri="smile:persistence/singleResourceProcessor?method=UPDATE&amp;partitionId=1"/>
    </route>
</routes>

Example Route: Delete Resource

The following example shows a route that deletes a FHIR resource based on messages in a kafka topic.

<routes xmlns="http://camel.apache.org/schema/spring">
    <route id="delete-resource">
        <from uri="kafka:resource-delete-topic?brokers=localhost:9092&amp;groupId=delete-group"/>
        <to uri="smile:persistence/singleResourceProcessor?method=DELETE"/>
    </route>
</routes>

FHIR Validator Processor

 

This processor validates the FHIR resource in the exchange body against the validation configuration (loaded Implementation Guides, profiles, and terminology) of a FHIR Storage module, without making an HTTP $validate round-trip. The resource is validated as-is and is never persisted. This is useful for screening resources flowing through a Camel route before they are stored or forwarded to another system.

  • Example URI: smile:persistence/validate
  • Input: IBaseResource – the FHIR resource to validate. A Bundle is validated as a single unit, producing one OperationOutcome.
  • Output: The processor always sets the following exchange variables:
    • outcome (configurable, see outcomeVariable) – an IBaseOperationOutcome containing the validation issues.
    • validationPassed (configurable, see validationSuccessVariable) – a Boolean that is true when the resource has no ERROR or FATAL issues.

The module id in the URI is required and names the persistence module whose validation configuration (loaded IGs/profiles, terminology) backs the validation.

Processor Parameters

The following parameters are available to this processor:

  • failOnSeverity – (optional) Specifies the issue severity at which the processor will throw an UnprocessableEntityException carrying the OperationOutcome. If the highest severity among the validation issues is at or above this level, the exception is thrown. Accepted values are INFORMATION, WARNING, ERROR (the default), FATAL, or none / never to disable throwing entirely (branch/inspect mode). The default of ERROR means the route fails when the resource has any ERROR or FATAL issue.
  • profile – (optional) The canonical URL of a profile to validate the resource against. When omitted, the resource is validated against the profiles declared in its own meta.profile.
  • replaceBody – (optional) When true, the exchange body is replaced with the OperationOutcome after validation. Value should be true or false (which is the default).
  • outcomeVariable – (optional) The name of the exchange variable that receives the OperationOutcome. Defaults to outcome.
  • validationSuccessVariable – (optional) The name of the exchange variable that receives the pass/fail Boolean. Defaults to validationPassed.

Example Route: Filter Invalid Resources

This is the default behaviour. Resources that fail validation cause the processor to throw an UnprocessableEntityException, which is handled by an onException block. Valid resources continue to the FHIR Storage module to be persisted.

<routes xmlns="http://camel.apache.org/schema/spring">
	<route id="validate-then-store">
		<from uri="kafka:resource-in-topic?brokers=localhost:9092&amp;groupId=validate-group"/>
		<onException>
			<exception>ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException</exception>
			<handled><constant>true</constant></handled>
			<!-- The OperationOutcome is available on the outcome variable -->
			<to uri="kafka:invalid-resource-topic?brokers=localhost:9092"/>
		</onException>
		<!-- Throws on any ERROR/FATAL issue (failOnSeverity defaults to ERROR) -->
		<to uri="smile:persistence/validate"/>
		<to uri="smile:persistence/singleResourceProcessor?method=CREATE"/>
	</route>
</routes>

Example Route: Branch on Validation Result

Setting failOnSeverity=none disables throwing, so the route can inspect the validationPassed variable and branch with a choice. Both valid and invalid resources are routed explicitly.

<routes xmlns="http://camel.apache.org/schema/spring">
	<route id="validate-and-branch">
		<from uri="kafka:resource-in-topic?brokers=localhost:9092&amp;groupId=branch-group"/>
		<!-- Never throws; always sets validationPassed and outcome -->
		<to uri="smile:persistence/validate?failOnSeverity=none"/>
		<choice>
			<when>
				<simple>${variable.validationPassed} == true</simple>
				<to uri="smile:persistence/singleResourceProcessor?method=CREATE"/>
			</when>
			<otherwise>
				<to uri="kafka:invalid-resource-topic?brokers=localhost:9092"/>
			</otherwise>
		</choice>
	</route>
</routes>

Example Route: Replace Body with OperationOutcome

Setting replaceBody=true replaces the exchange body with the OperationOutcome. This example validates against a specific profile and returns the outcome over an HTTP endpoint.

<routes xmlns="http://camel.apache.org/schema/spring">
	<route id="validate-and-return-outcome">
		<from uri="netty-http:http://0.0.0.0:8888/validate"/>
		<convertBodyTo type="org.hl7.fhir.instance.model.api.IBaseResource"/>
		<!-- Validate against an explicit profile and emit the OperationOutcome as the body -->
		<to uri="smile:persistence/validate?profile=http://example.org/fhir/StructureDefinition/my-patient&amp;failOnSeverity=none&amp;replaceBody=true"/>
	</route>
</routes>

ValueSet $expand Processor

 

The ValueSet $expand processor executes the FHIR ValueSet $expand operation to expand a ValueSet into a list of codes. This is useful for obtaining the full set of codes contained in a ValueSet for validation, display, or other purposes.

  • Example URI: smile:persistence/expandValueSetProcessor
  • Input: IBaseParameters containing the $expand operation parameters
  • Output: IBaseResource - An expanded ValueSet resource

Input Parameters

The following FHIR $expand parameters are supported:

  • url – (optional) The canonical URL of the ValueSet to expand
  • valueSet – (optional) An inline ValueSet resource to expand
  • valueSetVersion – (optional) The version of the ValueSet
  • filter – (optional) A text filter to apply to the expansion
  • offset – (optional) Pagination offset (integer)
  • count – (optional) Maximum number of codes to return (integer)
  • displayLanguage – (optional) Preferred display language (code)
  • includeHierarchy – (optional) Whether to include hierarchical information (boolean)

Either url or valueSet must be provided.

Example Route: Expand ValueSet from HTTP

The following example accepts ValueSet expansion requests via HTTP POST and returns the expanded ValueSet:

<routes xmlns="http://camel.apache.org/schema/spring">
    <route id="expand-valueset">
        <from uri="jetty:http://0.0.0.0:8888/expand-valueset"/>
        <to uri="smile:persistence/expandValueSetProcessor"/>
    </route>
</routes>

Example Route: Filter ValueSet Expansion

This example demonstrates expanding a ValueSet with a text filter applied. Users can POST a Parameters resource to the HTTP endpoint.

Input (HTTP POST body): A Parameters resource containing the ValueSet URL and filter text:

{
  "resourceType": "Parameters",
  "parameter": [
    {
      "name": "url",
      "valueUri": "http://hl7.org/fhir/ValueSet/condition-code"
    },
    {
      "name": "filter",
      "valueString": "diabetes"
    }
  ]
}

Output (HTTP response body): A ValueSet resource with an expansion containing only codes matching the filter:

{
  "resourceType": "ValueSet",
  "url": "http://hl7.org/fhir/ValueSet/condition-code",
  "expansion": {
    "identifier": "urn:uuid:12345678-1234-1234-1234-123456789abc",
    "timestamp": "2024-01-15T10:30:00Z",
    "contains": [
      {
        "system": "http://snomed.info/sct",
        "code": "73211009",
        "display": "Diabetes mellitus"
      },
      {
        "system": "http://snomed.info/sct",
        "code": "44054006",
        "display": "Type 2 diabetes mellitus"
      }
    ]
  }
}
<route id="expand-filtered-valueset">
  <from uri="netty-http:http://0.0.0.0:8889/expand-filtered"/>
  <convertBodyTo type="java.lang.String"/>
  <convertBodyTo type="org.hl7.fhir.instance.model.api.IBaseResource"/>
  <to uri="smile:persistence/expandValueSetProcessor"/>
</route>

Users can invoke this route using curl:

curl -X POST http://localhost:8889/expand-filtered \
  -H "Content-Type: application/fhir+json" \
  -d '{
    "resourceType": "Parameters",
    "parameter": [
      {
        "name": "url",
        "valueUri": "http://hl7.org/fhir/ValueSet/condition-code"
      },
      {
        "name": "filter",
        "valueString": "diabetes"
      }
    ]
  }'

ValueSet $validate-code Processor

 

The ValueSet $validate-code processor executes the FHIR ValueSet $validate-code operation to validate whether a code is in a given ValueSet.

  • Example URI: smile:persistence/valueSetValidateCodeProcessor
  • Input: IBaseParameters containing the $validate-code operation parameters
  • Output: IBaseParameters containing the validation result

Input Parameters

The following FHIR $validate-code parameters are supported:

  • url – (optional) The canonical URL of the ValueSet
  • code – (optional) The code to validate
  • system – (optional) The code system URL
  • systemVersion – (optional) The code system version
  • display – (optional) The expected display text
  • coding – (optional) A Coding element to validate
  • codeableConcept – (optional) A CodeableConcept to validate

Exchange Headers

The following Camel exchange headers are supported:

  • CamelFhirResourceId – (optional) The ValueSet resource ID for instance-level operations. Accepts either an IIdType or a String (e.g., "ValueSet/my-valueset-id"). When set, validation is performed against the specific ValueSet instance (equivalent to POST /ValueSet/[id]/$validate-code) rather than requiring a url parameter. This is useful when you want to validate against a ValueSet by its resource ID rather than its canonical URL.

Output Parameters

The processor returns a Parameters resource with the following values:

  • result – (boolean) true if the code is valid, false otherwise
  • message – (string) Additional information about the validation result
  • display – (string) The display text for the code

Example Route: Validate Code from Kafka

The following example validates codes received from a Kafka topic against a ValueSet:

<routes xmlns="http://camel.apache.org/schema/spring">
    <route id="validate-valueset-code">
        <from uri="kafka:code-validation-topic?brokers=localhost:9092&amp;groupId=validation-group"/>
        <!-- Input: Parameters with url, code, and system -->
        <to uri="smile:persistence/valueSetValidateCodeProcessor"/>
        <!-- Output: Parameters with result (true/false), message, display -->
    </route>
</routes>

Example Route: Validate and Route Based on Result

This example demonstrates validating a code and routing based on whether it's valid:

<routes xmlns="http://camel.apache.org/schema/spring">
    <route id="validate-and-route">
        <from uri="direct:validate-code"/>
        <to uri="smile:persistence/valueSetValidateCodeProcessor"/>
        <choice>
            <when>
                <simple>${body.getParameter('result').getValue().getValue()} == 'true'</simple>
                <to uri="direct:valid-codes"/>
            </when>
            <otherwise>
                <to uri="direct:invalid-codes"/>
            </otherwise>
        </choice>
    </route>
</routes>

Example Route: Instance-Level Validation

This example demonstrates validating a code against a specific ValueSet instance using its resource ID rather than its canonical URL. The CamelFhirResourceId header specifies the ValueSet to validate against:

<routes xmlns="http://camel.apache.org/schema/spring">
    <route id="validate-code-by-valueset-id">
        <from uri="direct:validate-code-instance"/>
        <!-- Set the ValueSet resource ID for instance-level operation -->
        <setHeader name="CamelFhirResourceId">
            <constant>ValueSet/1114</constant>
        </setHeader>
        <!-- Input: Parameters with code and system (url not required when using resource ID) -->
        <to uri="smile:persistence/valueSetValidateCodeProcessor"/>
        <!-- Output: Parameters with result (true/false), message, display -->
    </route>
</routes>

CodeSystem $validate-code Processor

 

The CodeSystem $validate-code processor executes the FHIR CodeSystem $validate-code operation to validate whether a code exists in a given CodeSystem. Unlike the ValueSet version, this validates directly against a CodeSystem without composition rules.

  • Example URI: smile:persistence/codeSystemValidateCodeProcessor
  • Input: IBaseParameters containing the $validate-code operation parameters
  • Output: IBaseParameters containing the validation result

Input Parameters

The following FHIR $validate-code parameters are supported:

  • url – (optional) The canonical URL of the CodeSystem
  • code – (optional) The code to validate
  • display – (optional) The expected display text
  • coding – (optional) A Coding element to validate
  • codeableConcept – (optional) A CodeableConcept to validate
  • version – (optional) The CodeSystem version

Exchange Headers

The following Camel exchange headers are supported:

  • CamelFhirResourceId – (optional) The CodeSystem resource ID for instance-level operations. Accepts either an IIdType or a String (e.g., "CodeSystem/my-codesystem-id"). When set, validation is performed against the specific CodeSystem instance (equivalent to POST /CodeSystem/[id]/$validate-code) rather than requiring a url parameter. This is useful when you want to validate against a CodeSystem by its resource ID rather than its canonical URL.

Output Parameters

The processor returns a Parameters resource with the following values:

  • result – (boolean) true if the code is valid, false otherwise
  • message – (string) Additional information about the validation result
  • display – (string) The display text for the code

Example Route: Validate CodeSystem Code (Type-Level)

The following example validates codes directly against a CodeSystem:

<routes xmlns="http://camel.apache.org/schema/spring">
    <route id="validate-codesystem-code">
        <from uri="direct:validate-codesystem"/>
        <!-- Input: Parameters with url and code -->
        <to uri="smile:persistence/codeSystemValidateCodeProcessor"/>
        <!-- Output: Parameters with result (true/false), message, display -->
    </route>
</routes>

Example Route: Instance-Level Validation

This example demonstrates validating a code against a specific CodeSystem instance using its resource ID rather than its canonical URL. The CamelFhirResourceId header specifies the CodeSystem to validate against:

<routes xmlns="http://camel.apache.org/schema/spring">
    <route id="validate-code-by-codesystem-id">
        <from uri="direct:validate-codesystem-instance"/>
        <!-- Set the CodeSystem resource ID for instance-level operation -->
        <setHeader name="CamelFhirResourceId">
			  <constant>CodeSystem/123</constant>
        </setHeader>
        <!-- Input: Parameters with code (url not required when using resource ID) -->
        <to uri="smile:persistence/codeSystemValidateCodeProcessor"/>
        <!-- Output: Parameters with result (true/false), message, display -->
    </route>
</routes>

Comparison: ValueSet vs CodeSystem Validation

AspectValueSet $validate-codeCodeSystem $validate-code
Validates AgainstValueSet (may include multiple CodeSystems)Single CodeSystem
Composition RulesApplies ValueSet include/exclude rulesDirect code lookup only
Use CaseValidate against clinical value setsValidate code exists in system
Processor URIvalueSetValidateCodeProcessorcodeSystemValidateCodeProcessor

CodeSystem $lookup Processor

 

The CodeSystem $lookup processor executes the FHIR CodeSystem $lookup operation to retrieve details about a specific code in a CodeSystem. This can be used to obtain display text, properties, and other information about a code.

  • Example URI: smile:persistence/codeSystemLookupProcessor
  • Input: IBaseParameters containing the $lookup operation parameters
  • Output: IBaseParameters containing the lookup result

Input Parameters

The following FHIR $lookup parameters are supported:

  • code – (optional) The code to look up
  • system – (optional) The code system URL
  • coding – (optional) A Coding element to look up
  • version – (optional) The version of the code system
  • displayLanguage – (optional) The preferred display language (code)
  • property – (optional, repeating) Specific properties to return for the code

Either code and system, or coding must be provided.

Output Parameters

The processor returns a Parameters resource with the following values:

  • name – (string) The name of the code system
  • version – (string) The version of the code system
  • display – (string) The display text for the code
  • abstract – (boolean) Whether the code is abstract
  • property – (part) Properties of the code, each containing sub-parameters for the property code, value, and description

Example Route: Look Up Code from Kafka

The following example looks up codes received from a Kafka topic:

<routes xmlns="http://camel.apache.org/schema/spring">
    <route id="lookup-code">
        <from uri="kafka:code-lookup-topic?brokers=localhost:9092&amp;groupId=lookup-group"/>
        <!-- Input: Parameters with code and system -->
        <to uri="smile:persistence/codeSystemLookupProcessor"/>
        <!-- Output: Parameters with name, version, display, property -->
    </route>
</routes>

Example Route: Look Up Code via HTTP

This example accepts lookup requests via HTTP POST. Users can submit a Parameters resource containing the code and system to look up:

Input (HTTP POST body):

{
  "resourceType": "Parameters",
  "parameter": [
    {
      "name": "code",
      "valueCode": "39156-5"
    },
    {
      "name": "system",
      "valueUri": "http://loinc.org"
    }
  ]
}
<routes xmlns="http://camel.apache.org/schema/spring">
    <route id="lookup-code-http">
        <from uri="netty-http:http://0.0.0.0:8888/lookup-code"/>
        <convertBodyTo type="java.lang.String"/>
        <convertBodyTo type="org.hl7.fhir.instance.model.api.IBaseResource"/>
        <to uri="smile:persistence/codeSystemLookupProcessor"/>
    </route>
</routes>

Users can invoke this route using curl:

curl -X POST http://localhost:8888/lookup-code \
  -H "Content-Type: application/fhir+json" \
  -d '{
    "resourceType": "Parameters",
    "parameter": [
      {
        "name": "code",
        "valueCode": "39156-5"
      },
      {
        "name": "system",
        "valueUri": "http://loinc.org"
      }
    ]
  }'

ConceptMap $translate Processor

 

The ConceptMap $translate processor executes the FHIR ConceptMap $translate operation to translate codes between code systems using ConceptMap resources. This is useful for mapping codes from one system to another, such as translating from a local code system to a standard terminology.

  • Example URI: smile:persistence/conceptMapTranslateProcessor
  • Input: IBaseParameters containing the $translate operation parameters
  • Output: IBaseParameters containing the translation result

Input Parameters

The following FHIR $translate parameters are supported:

  • url – (optional) The canonical URL of the ConceptMap to use
  • conceptMapVersion – (optional) The version of the ConceptMap
  • code – (optional) The source code to translate
  • system – (optional) The source code system URL
  • version – (optional) The source code system version
  • coding – (optional) A Coding element to translate
  • codeableConcept – (optional) A CodeableConcept to translate
  • source – (optional) The source ValueSet URL (limits translation scope)
  • target – (optional) The target ValueSet URL (limits translation scope)
  • targetsystem – (optional) The target code system URL
  • reverse – (optional) Whether to reverse the mapping direction (boolean)

Either code and system, or coding, or codeableConcept must be provided.

Exchange Headers

The following Camel exchange headers are supported:

  • CamelFhirResourceId – (optional) The ConceptMap resource ID for instance-level operations. Accepts either an IIdType or a String (e.g., "ConceptMap/123"). When set, translation is performed against the specific ConceptMap instance (equivalent to POST /ConceptMap/[id]/$translate) rather than requiring a url parameter. This is useful when you want to translate using a ConceptMap by its resource ID rather than its canonical URL.

Output Parameters

The processor returns a Parameters resource with the following values:

  • result – (boolean) true if a translation was found, false otherwise
  • message – (string) Error or informational message about the translation
  • match – (part) Translation match entries, each containing sub-parameters for the equivalence, concept (Coding), and source (uri)

Example Route: Translate Code from Kafka

The following example translates codes received from a Kafka topic:

<routes xmlns="http://camel.apache.org/schema/spring">
    <route id="translate-code">
        <from uri="kafka:code-translate-topic?brokers=localhost:9092&amp;groupId=translate-group"/>
        <!-- Input: Parameters with code, system, and target -->
        <to uri="smile:persistence/conceptMapTranslateProcessor"/>
        <!-- Output: Parameters with result, message, match -->
    </route>
</routes>

Example Route: Translate and Route Based on Result

This example demonstrates translating a code and routing based on whether a translation was found:

<routes xmlns="http://camel.apache.org/schema/spring">
    <route id="translate-and-route">
        <from uri="direct:translate-code"/>
        <to uri="smile:persistence/conceptMapTranslateProcessor"/>
        <choice>
            <when>
                <simple>${body.getParameter('result').getValue().getValue()} == 'true'</simple>
                <to uri="direct:translation-found"/>
            </when>
            <otherwise>
                <to uri="direct:no-translation"/>
            </otherwise>
        </choice>
    </route>
</routes>

Example Route: Instance-Level Translation

This example demonstrates translating a code using a specific ConceptMap instance identified by its resource ID rather than its canonical URL. The CamelFhirResourceId header specifies the ConceptMap to use:

<routes xmlns="http://camel.apache.org/schema/spring">
    <route id="translate-code-by-conceptmap-id">
        <from uri="direct:translate-code-instance"/>
        <!-- Set the ConceptMap resource ID for instance-level operation -->
        <setHeader name="CamelFhirResourceId">
            <constant>ConceptMap/123</constant>
        </setHeader>
        <!-- Input: Parameters with code and system (url not required when using resource ID) -->
        <to uri="smile:persistence/conceptMapTranslateProcessor"/>
        <!-- Output: Parameters with result, message, match -->
    </route>
</routes>

In-Place Translate Processor

 

The In-Place Translate processor applies in-place code translation to a FHIR resource flowing through a Camel route. It translates all codings on the resource to a configured target code system using ConceptMap $translate, adding translated codings alongside the originals. The (possibly modified) resource is placed back onto the exchange body.

  • Example URI: smile:persistence/inplaceTranslateProcessor?targetSystem=http://loinc.org
  • Input: IBaseResource – the FHIR resource whose codings should be translated
  • Output: IBaseResource – the resource after translation (original codings preserved; translated coding added if found)

URI Parameters

  • targetSystem – (required) The target code system URI (e.g., http://loinc.org). Validated at route startup.

Example Route: Translate Observations from Kafka

The following Camel route reads Observations from a Kafka topic, applies in-place LOINC translation, and stores the result:

<routes xmlns="http://camel.apache.org/schema/spring">
    <route id="inplace-translate-observation">
        <from uri="kafka:observations-in?brokers=localhost:9092&amp;groupId=translate-group&amp;maxPollRecords=1&amp;allowManualCommit=true&amp;autoCommitEnable=false"/>
        <!-- Translate codings to LOINC in-place -->
        <to uri="smile:persistence/inplaceTranslateProcessor?targetSystem=http://loinc.org"/>
        <!-- Persist the (possibly translated) resource -->
        <to uri="smile:persistence/singleResourceProcessor"/>
        <to uri="smile:clustermgr/kafkaManualCommit"/>
    </route>
</routes>

For the complete feature description, including the alternative storage interceptor activation method, see In-Place Code Translation.

Patient $merge Processor

 

The Resource $merge processor executes the FHIR Patient $merge operation to merge two patient records. This operation merges source patient data into a target patient, updating all references and marking the source patient as inactive or optionally deleting the source patient.

  • Example URI: smile:persistence/patientMergeProcessor
  • Input: IBaseParameters containing the $merge operation parameters
  • Output: IBaseParameters containing the merge result

Input Parameters

The following FHIR $merge parameters are supported:

  • source-patient – (optional) Reference to the source patient to be merged
  • source-patient-identifier – (optional) Identifier of the source patient
  • target-patient – (optional) Reference to the target patient that will receive the merged data
  • target-patient-identifier – (optional) Identifier of the target patient
  • preview – (optional) Boolean indicating whether to preview without persisting changes (default: false)
  • delete-source – (optional) Boolean indicating whether to delete the source patient after merge (default: false)
  • result-patient – (optional) Patient resource to use as the merge result

Either source-patient or source-patient-identifier must be provided. Similarly, either target-patient or target-patient-identifier must be provided.

Output Parameters

The processor returns a Parameters resource with the following values:

  • input – (Parameters) The original input parameters
  • outcome – (OperationOutcome) Status and messages from the merge operation
  • result – (Patient) The updated target patient after merge (not included in preview mode)

Limitations

  • Asynchronous processing – The Prefer: respond-async header is not supported for merge operations via Camel. Requests containing this header will be rejected with an error. All merge operations are processed synchronously.

Example Route: Merge Patients from Kafka

The following example merges patient records received from a Kafka topic:

<routes xmlns="http://camel.apache.org/schema/spring">
    <route id="merge-patients">
        <from uri="kafka:patient-merge-topic?brokers=localhost:9092&amp;groupId=merge-group"/>
        <!-- Input: Parameters with source-patient and target-patient -->
        <to uri="smile:persistence/patientMergeProcessor"/>
        <!-- Output: Parameters with outcome and merged result -->
    </route>
</routes>

Example Route: Preview Merge Before Execution

This example demonstrates using preview mode to validate a merge before actually executing it:

<routes xmlns="http://camel.apache.org/schema/spring">
    <route id="preview-merge">
        <from uri="direct:preview-patient-merge"/>
		 <!-- Input: Parameters including preview=true -->
        <to uri="smile:persistence/patientMergeProcessor"/>
        <choice>
            <when>
                <!-- Check if preview was successful -->
                <simple>${body.getParameter('outcome').getResource().getIssue().isEmpty()}</simple>
                <!-- If successful, execute actual merge -->
                <to uri="direct:execute-patient-merge"/>
            </when>
            <otherwise>
                <to uri="direct:merge-failed"/>
            </otherwise>
        </choice>
    </route>
</routes>

$replace-references Processor

 

The $replace-references processor executes the FHIR $replace-references operation to update all references from a source resource to a target resource.

  • Example URI: smile:persistence/replaceReferencesProcessor
  • Input: IBaseParameters containing the operation parameters
  • Output: IBaseParameters containing the operation result

Input Parameters

The following parameters are supported for the $replace-references operation:

  • source-reference-id – (required) String containing the ID of the resource whose references should be replaced (e.g., "Patient/123")
  • target-reference-id – (required) String containing the ID of the resource that should become the new reference target (e.g., "Patient/456")
  • resource-limit – (optional) Unsigned integer specifying the maximum number of resources to update (default: 512, maximum: 10000)

Output Parameters

The processor returns a Parameters resource with the following values:

  • outcome – (Bundle) Bundle of PATCH operations performed

The processor also sets the HTTP response code header:

  • 200 - Operation completed successfully

Example Route: Replace References with Limit

This example demonstrates replacing references with a resource limit to control the scope of updates:

<routes xmlns="http://camel.apache.org/schema/spring">
    <route id="replace-references-limited">
        <from uri="kafka:reference-replacement-topic?brokers=localhost:9092&amp;groupId=replace-group"/>
        <!-- Input: Parameters with source-reference-id, target-reference-id, resource-limit -->
        <to uri="smile:persistence/replaceReferencesProcessor"/>
        <to uri="direct:process-sync-result"/>
    </route>
</routes>

Example Route: Guaranteed Delivery with Manual Commit

This example shows reference replacement with guaranteed delivery using Kafka manual commit:

<routes xmlns="http://camel.apache.org/schema/spring">
    <route id="replace-references-guaranteed">
        <from uri="kafka:replace-refs-topic?brokers=localhost:9092&amp;groupId=replace-refs-group&amp;maxPollRecords=1&amp;allowManualCommit=true&amp;autoCommitEnable=false&amp;autoOffsetReset=earliest"/>
        <to uri="smile:persistence/replaceReferencesProcessor"/>
        <to uri="smile:clustermgr/kafkaManualCommit"/>
    </route>
</routes>