Outbound: Custom Resource Conversion
Custom Outbound Resource Conversion relies on a custom Java-based message converter class that you define. This converter class can take advantage of Smile CDR's built-in message conversion routines if you want, but it can also use individual segment converters or rely completely on custom conversion. As a result, it can create any HL7 v2.x message type and is very flexible.
It relies on a FHIR Subscription to trigger messages. This can be a subscription on business data resources as is the case with Default Outbound Resource Conversion but it can also rely on MessageHeader resources, which can be useful if you want your outbound HL7 v2.x messages to correspond to real-world business workflows as opposed to data-level changes.
If you are designing a solution where you are performing storage actions as a part of a larger business transaction, and you would like that business transaction to dictate the type and contents of the generated HL7 v2.x message, using a MessageHeader resource can be a good option.
For example, suppose you have a system which often stores Encounter resources, and you want these to generate either an ADT^A01
, ADT^A04
, or ADT^A08
message depending on the business context. In this case, you can use a FHIR Transaction to store both the Encounter and any other resources relevant to the workflow. In addition to these resources, you can also store a new MessageHeader resource which will ultimately trigger the outbound message. This MessageHeader resource is passed to your custom mapper class, which uses it to determine which type of message to send and what it should contain.
The MessageHeader.focus
element is used to signal the relevant FHIR resources for the given transaction. If you include versioned references here, you can also ensure that the correct version of these resources are used to generate the outbound message which is useful if you expect frequent updates to resources and want your HL7 v2.x messages to accurately represent the complete history of changes to data in order. Enabling Automatically Version References on this field can help to make this versioning automatic as a part of your FHIR transactions.
A simple example MessageHeader is shown below. Note that this is just an example, and the contents and format are complete up to you since your converter class is fully responsible for interpreting these values.
{
"resType": "MessageHeader",
"eventCoding": {
"system": "http://terminology.hl7.org/CodeSystem/v2-0003",
"code": "A01"
},
"focus": [ {
"reference": "Patient/123/_history/5"
}, {
"reference": "Encounter/555/_history/2"
} ]
}
The custom mapper class is a class you create, which implements the IHl7V2OutboundMapperSvc interface.
An example is shown below:
public class MessageHeaderCustomMessageMapperExample implements IHl7V2OutboundCustomMapper {
@Override
public List<MappingTarget<?>> convert(ConversionContext theConversionContext) throws HL7Exception {
IHl7V2OutboundMapperSvc outboundMapperSvc = theConversionContext.getHl7V2OutboundMapperSvc();
MessageHeader focalMessageHeader = (MessageHeader) theConversionContext.getFocalResource();
// Create a mapping instructions file - this can be used to supply settings
// to the built-in Smile conversion algorithms. Here we will apply versions
// from the MessageHeader, meaning that if we need to resolve any resources
// in the process of applying Smile default mappings, we will use the specific
// versions called for in the MessageHeader
OutboundMappingInstructions mappingInstructions;
mappingInstructions = new OutboundMappingInstructions.Builder()
.addSourceDataResourceVersionsFromMessageHeaderFocus(focalMessageHeader)
.build();
MappingTarget<ADT_A05> mappingTarget = outboundMapperSvc.newTarget("ADT", "A28", ADT_A05.class);
// Resolve the Encounter using the reference found in the MessageHeader
Encounter encounter = theConversionContext.resolveFirstResourceFromMessageHeaderFocus(Encounter.class, focalMessageHeader);
// In this example we'll use the default Smile CDR algorithm to populate
// the entire message
outboundMapperSvc.populateMessageAdtA05FromEncounter(encounter, mappingTarget, mappingInstructions);
// We can also add additional mappings if we want
mappingTarget.getMessage().getUB1().getUb11_SetIDUB1().setValue("1");
mappingTarget.getMessage().getUB1().getUb12_BloodDeductible().setValue("22");
// We're always returning one message here, but the converter could technically
// produce multiple messages or even zero messages.
return List.of(mappingTarget);
}
}
You can also manually construct messages using individual segment converters only, and skip the complete built-in message conversion routine, as shown in the following example:
public class MessageHeaderCustomSegmentMapperExample implements IHl7V2OutboundCustomMapper {
@Override
public List<MappingTarget<?>> convert(ConversionContext theConversionContext) throws HL7Exception {
IHl7V2OutboundMapperSvc outboundMapperSvc = theConversionContext.getHl7V2OutboundMapperSvc();
MessageHeader focalMessageHeader = (MessageHeader) theConversionContext.getFocalResource();
// Create a mapping instructions file - this can be used to supply settings
// to the built-in Smile conversion algorithms. Here we will apply versions
// from the MessageHeader, meaning that if we need to resolve any resources
// in the process of applying Smile default mappings, we will use the specific
// versions called for in the MessageHeader
OutboundMappingInstructions mappingInstructions;
mappingInstructions = new OutboundMappingInstructions.Builder()
.addSourceDataResourceVersionsFromMessageHeaderFocus(focalMessageHeader)
.build();
MappingTarget<ADT_A01> mappingTarget = outboundMapperSvc.newTarget("ADT", "A01", ADT_A01.class);
ADT_A01 message = mappingTarget.getMessage();
// Resolve the Patient and Encounter using the reference found in the MessageHeader
Patient patient = theConversionContext.resolveFirstResourceFromMessageHeaderFocus(Patient.class, focalMessageHeader);
Encounter encounter = theConversionContext.resolveFirstResourceFromMessageHeaderFocus(Encounter.class, focalMessageHeader);
// In this example we'll use the default Smile CDR algorithm to populate
// the PID and PV1 segments
outboundMapperSvc.populateSegmentPidFromPatient(patient, encounter, message.getPID(), mappingTarget, mappingInstructions);
outboundMapperSvc.populateSegmentPv1FromEncounter(encounter, message.getPV1(), mappingTarget, mappingInstructions);
// We'll also manually populate the EVN segment. Other segments could be
// populated in the same way
EVN evn = message.getEVN();
evn.getEvn1_EventTypeCode().setValue("A01");
// We're always returning one message here, but the converter could technically
// produce multiple messages or even zero messages.
return List.of(mappingTarget);
}
}
An example Subscription is shown below:
{
"resourceType": "Subscription",
"id": "SUBSID",
"status": "requested",
"criteria": "DiagnosticReport?",
"channel": {
"extension": [
{
"url": "https://smilecdr.com/fhir/ns/StructureDefinition/subscription-channel-rest-delivery-class",
"valueString": "ca.cdr.endpoint.hl7v2.out.deliverer.OutboundHl7V2SubscriptionDeliverer"
},
{
"url": "https://smilecdr.com/fhir/ns/StructureDefinition/subscription-channel-hl7v2-sender-module-id",
"valueString": "hl7_sender"
},
{
"url": "https://smilecdr.com/fhir/ns/StructureDefinition/subscription-channel-hl7v2-custom-mapper-class",
"valueCode": "org.example.MessageHeaderCustomSegmentMapperExample"
}
],
"type": "rest-hook"
}
}
The subscription-channel-hl7v2-custom-mapper-class
extension contains the fully-qualified class name of your custom mapper class.