This page describes the modules and files that are shared by every CMS API that records its transactions in the trace repository:
Communication and Binary resources.Communication resource.Each CMS API adds its own Transaction Log Broker module and JQ transformer. The examples on this page use the module IDs repo_trace, http_trace, and camel_trace, and are written for Properties mode.
Create a FHIR Storage (R4 RDBMS) module for the trace repository. Use a database that is separate from the databases holding member data, so that trace data can be retained, backed up, and secured on its own schedule.
module.repo_trace.type=PERSISTENCE_R4
module.repo_trace.config.db.driver=POSTGRES_9_4
module.repo_trace.config.db.url=jdbc:postgresql://localhost:5432/cdr_trace
module.repo_trace.config.db.username=#{env['DB_USER']}
module.repo_trace.config.db.password=#{env['DB_PASS']}
module.repo_trace.config.db.schema_update_mode=UPDATE
The database URL and credentials are examples. Clients search the trace repository by Communication tag and identifier, so do not disable the default _tag and identifier search parameters for Communication.
Create a FHIR Endpoint module for the trace repository. The Camel module posts each Bundle to this endpoint, and reporting clients read the stored resources from it.
module.http_trace.type=ENDPOINT_FHIR_REST
module.http_trace.requires.PERSISTENCE_ALL=repo_trace
module.http_trace.requires.SECURITY_IN_UP=local_security
module.http_trace.config.port=8008
module.http_trace.config.context_path=/trace-fhir
module.http_trace.config.anonymous.access.enabled=false
module.http_trace.config.security.http.basic.enabled=true
Replace repo_trace with the module ID of your trace repository, and local_security with an inbound security module, for example a Local Inbound Security module, that holds the system user the Camel module authenticates as. Anonymous access is disabled because the trace repository contains PHI, and HTTP Basic authentication is enabled for the Camel module; reporting clients can use another security module, for example a SMART on FHIR Inbound Security module added with SECURITY_IN_OIC. The port and context path are examples; the Camel route posts to this address.
The Camel module authenticates to the trace endpoint as a system user. Create the user in the inbound security module of the trace endpoint, with the following permissions:
| Permission | Argument | Purpose |
|---|---|---|
ROLE_FHIR_CLIENT |
Access the FHIR endpoint. | |
FHIR_TRANSACTION |
Submit the transaction Bundle. | |
FHIR_WRITE_ALL_OF_TYPE |
Communication |
Create the Communication resource. |
FHIR_WRITE_ALL_OF_TYPE |
Binary |
Create the Binary resources. |
The examples on this page use the username SYSTEM-USER-TXLOG, and read its password from the JS_SYSTEM_USER_PASSWORD environment variable.
A Camel module consumes the transaction log topic and posts each event to the trace endpoint. Each CMS API can have its own Camel module, or several APIs can share one module with a route for each topic.
JQ transformers run with the Apache Camel JQ language, which is not included in Smile CDR. Add the following jar files to the smilecdr/customerlib folder, as described in Adding Other Camel Components:
org.apache.camel:camel-jqorg.apache.camel:camel-jacksonnet.thisptr:jackson-jqUse the camel-jq and camel-jackson versions that match the Camel version of your Smile CDR release, and the jackson-jq version that camel-jq declares as a dependency.
module.camel_trace.type=CAMEL
module.camel_trace.config.routes.script.file=classpath:/config_seeding/transaction-log-processor.xml
module.camel_trace.config.functions.script.file=classpath:/config_seeding/transaction-log-processor.js
module.camel_trace.config.enable_transaction_log_on_all_routes=false
routes.script.file – The Camel routes, described below.functions.script.file – The JavaScript functions called by the routes, described below.enable_transaction_log_on_all_routes – Set to false. A route that consumes a transaction log topic must not write its own transaction log events, or each event it processes can create another event on the same topic. See Transaction Logging Processors.Place the routes and functions files, and any JQ transformers that you write yourself, in the smilecdr/classes folder, so that the classpath: paths above resolve. For example, classpath:/config_seeding/transaction-log-processor.xml resolves to smilecdr/classes/config_seeding/transaction-log-processor.xml.
The following routes consume the topic channel_cms_txlog, map each event with a single JQ transformer, and post the result to the trace endpoint:
<routes xmlns="http://camel.apache.org/schema/spring">
<route>
<from uri="smile:clustermgr/broker?topic=channel_cms_txlog"/>
<convertBodyTo type="java.lang.String"/>
<doTry>
<transform>
<jq>resource:classpath:/txlogmap/my-api/transformer.jq</jq>
</transform>
<doCatch>
<exception>java.lang.Exception</exception>
<log logName="ca.cdr.camel.txlog" message="Error in JQ transform: ${exception.message}" loggingLevel="ERROR"/>
<setProperty name="errorMessage">
<simple>${exception.message}</simple>
</setProperty>
<transform>
<jq>resource:classpath:/txlogmap/my-api/handle-error.jq</jq>
</transform>
</doCatch>
</doTry>
<transform>
<jq>resource:classpath:/txlogmap/wrap-bundle.jq</jq>
</transform>
<to uri="direct:postCommunicationResource"/>
</route>
<route>
<from uri="direct:postCommunicationResource"/>
<convertBodyTo type="java.lang.String"/>
<setHeader name="CamelHttpMethod">
<constant>POST</constant>
</setHeader>
<setHeader name="CamelHttpPath">
<constant></constant>
</setHeader>
<setHeader name="Content-Type">
<constant>application/json</constant>
</setHeader>
<to uri="smile:camel/script?function=setBasicAuthHeaderTXLOG&includeExchange=true"/>
<to uri="http://localhost:8008/trace-fhir?bridgeEndpoint=true"/>
</route>
</routes>
smile:clustermgr/broker?topic=... – The broker consumer. Set topic to the broker.channel_name of the Transaction Log Broker module.doTry / doCatch – When the transformer fails, the error transformer creates a Communication that records the error message, so that the event is still traced. See Error Transformer.CamelHttpPath – Cleared, so that the Bundle is posted to the base URL of the trace endpoint rather than to a path carried over from the consumed message.http://localhost:8008/trace-fhir – The base URL of the trace endpoint. bridgeEndpoint=true makes the HTTP component use this URL as configured.The postCommunicationResource route calls the following function with the Script processor, to authenticate as the system user:
function setBasicAuthHeaderTXLOG(message, exchange) {
const username = 'SYSTEM-USER-TXLOG';
const password = Environment.getEnv('JS_SYSTEM_USER_PASSWORD');
const credentials = username + ':' + password;
const encodedCredentials = Converter.base64Encode(credentials);
exchange.headers['Authorization'] = 'Basic ' + encodedCredentials;
}
The function reads the password with the Environment API, which only reads environment variables whose names begin with js_ or js. (in any case), for example JS_SYSTEM_USER_PASSWORD. Set the JS_SYSTEM_USER_PASSWORD environment variable on every Smile CDR node that runs the Camel module.
When a topic carries more than one kind of event, each kind usually needs its own transformer. A function can choose the transformer from the event's type and subtype, and store its name in an exchange property that the route uses to call the transformer:
const transformers = {
"FHIR_REQUEST|FHIR_OPERATION_MEMBER_MATCH": "p2p-member-match",
"FHIR_REQUEST|PA_PAS_CLAIM_SUBMIT": "pa-pas-operations"
};
function getTransformer(theRequest, exchange) {
const key = theRequest.payload.type + "|" + theRequest.payload.subType;
const transformer = transformers[key];
if (!transformer) {
throw new Error('No transformer found for type: ' + key);
}
exchange.properties['transformer'] = transformer;
}
In the consuming route, call the function before the doTry, and replace the transform inside the doTry with a call to a route for the selected transformer:
<to uri="smile:camel/script?function=getTransformer&includeExchange=true&input=json"/>
<convertBodyTo type="java.lang.String"/>
<doTry>
<toD uri="direct:${exchangeProperty.transformer}"/>
<doCatch>
<!-- as above -->
</doCatch>
</doTry>
Then add one route for each transformer:
<route>
<from uri="direct:p2p-member-match"/>
<transform>
<jq>resource:classpath:/txlogmap/p2p-member-match/transformer.jq</jq>
</transform>
</route>
The function receives the event as JSON (input=json). When every event on a topic uses the same transformer, as in the routes above, no selection function is needed.
Events with no transformer are not traced, and are logged as No transformer found for type. To skip event types on purpose, set exchange.properties['skipTxlog'] = 'true' and return instead of throwing, then stop the exchange:
<to uri="smile:camel/script?function=getTransformer&includeExchange=true&input=json"/>
<filter>
<simple>${exchangeProperty.skipTxlog} == 'true'</simple>
<stop/>
</filter>
The routes map each event with a JQ transformer. Smile CDR provides the transformers for its CMS APIs and the shared wrap-bundle.jq transformer. See JQ Transformers for the provided transformers, and for writing your own.
Communication resources are created. Confirm that the route's topic matches the Transaction Log Broker module's broker.channel_name, and that the broker module's event_whitelist selects the events. Set the ca.cdr.camel.txlog logger to DEBUG to log each step of the route.Error in JQ transform and No transformer found for type.401 Unauthorized or 403 Forbidden. Confirm that JS_SYSTEM_USER_PASSWORD is set on the node, that HTTP Basic authentication is enabled on the trace endpoint, and that the system user has the permissions above.Communication resources are tagged error. The note holds the error raised by the transformer. A common cause is a transformer that reads a request or response body that the Transaction Log Broker module does not publish.jq language cannot be found. The JQ libraries are missing from smilecdr/customerlib, or their version does not match the Camel version of Smile CDR.You are about to leave the Smile Digital Health documentation and navigate to the Open Source HAPI-FHIR Documentation.