001/*- 002 * #%L 003 * Smile CDR - CDR 004 * %% 005 * Copyright (C) 2016 - 2024 Smile CDR, Inc. 006 * %% 007 * All rights reserved. 008 * #L% 009 */ 010package ca.cdr.api.pub.hl7v2.out; 011 012import ca.uhn.fhir.context.FhirContext; 013import ca.uhn.fhir.jpa.api.dao.DaoRegistry; 014import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; 015import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription; 016import ca.uhn.fhir.rest.api.server.SystemRequestDetails; 017import ca.uhn.hl7v2.HL7Exception; 018import org.apache.commons.lang3.Validate; 019import org.hl7.fhir.instance.model.api.IBaseReference; 020import org.hl7.fhir.instance.model.api.IBaseResource; 021import org.hl7.fhir.instance.model.api.IIdType; 022 023import java.util.List; 024 025/** 026 * This interface is intended to be implemented by custom HL7v2 outbound mapper 027 * classes. Implementations receive a focal resource (which was the target of 028 * a Subscription) and produce a {@link MappingTarget} containing an HL7v2 029 * message to send. 030 */ 031public interface IHl7V2OutboundCustomMapper { 032 033 /** 034 * Convert a resource into a collection of HL7v2 message(s) to send. 035 * <p> 036 * Note that transmission of the generated messages to the receiving system will happen synchronously, 037 * in the order that the messages are returned in the list. If multiple messages are returned and a message 038 * fails to be sent (i.e. because the receiving system rejects it or is unreachable), the entire collection 039 * may be resent, including messages which have already previously been sent. 040 * </p> 041 * 042 * @param theConversionContext An object containing various information about the requested conversion 043 * @return A collection of {@link MappingTarget} instances containing HL7v2 message(s) to send. Implementations return an empty list or {@literal null} if they wish to not send any message. 044 */ 045 List<MappingTarget<?>> convert(ConversionContext theConversionContext) throws HL7Exception; 046 047 /** 048 * This class contains parameters for the {@link ConversionContext} method. 049 */ 050 class ConversionContext { 051 052 private final CanonicalSubscription mySubscription; 053 private final IBaseResource myFocalResource; 054 private final IHl7V2OutboundMapperSvc myHl7V2OutboundMapperSvc; 055 private final DaoRegistry myDaoRegistry; 056 057 /** 058 * Constructor 059 */ 060 public ConversionContext( 061 CanonicalSubscription theSubscription, 062 IBaseResource theFocalResource, 063 IHl7V2OutboundMapperSvc theHl7V2OutboundMapperSvc, 064 DaoRegistry theDaoRegistry) { 065 mySubscription = theSubscription; 066 myFocalResource = theFocalResource; 067 myHl7V2OutboundMapperSvc = theHl7V2OutboundMapperSvc; 068 myDaoRegistry = theDaoRegistry; 069 } 070 071 /** 072 * @return The outbound mapper service, which is used to create {@link MappingTarget} instances, and can be used to apply Smile CDR default segment or message mappings. 073 */ 074 public IHl7V2OutboundMapperSvc getHl7V2OutboundMapperSvc() { 075 return myHl7V2OutboundMapperSvc; 076 } 077 078 /** 079 * @return The FHIR subscription which triggered this action 080 */ 081 public CanonicalSubscription getSubscription() { 082 return mySubscription; 083 } 084 085 /** 086 * @return The resource which triggered the Subscription and caused this conversion 087 */ 088 public IBaseResource getFocalResource() { 089 return myFocalResource; 090 } 091 092 /** 093 * This method may be used to resolve a resource from storage 094 */ 095 @SuppressWarnings({"unchecked", "rawtypes"}) 096 public <T extends IBaseResource> T resolveResource(IIdType theResourceId) { 097 Validate.notNull(theResourceId, "theResourceId must not be null"); 098 Validate.isTrue(theResourceId.hasResourceType(), "theResourceId must contain a resource type"); 099 Validate.isTrue(theResourceId.hasIdPart(), "theResourceId must contain a resource ID"); 100 101 IFhirResourceDao dao = myDaoRegistry.getResourceDao(theResourceId.getResourceType()); 102 return (T) dao.read(theResourceId, new SystemRequestDetails()); 103 } 104 105 /** 106 * Given a MessageHeader resource in {@literal theMessageHeader}, searches all 107 * repetitions of {@literal MessageHeader.focus} for references to resource of 108 * type {@literal theResourceType}, then resolves and returns the first. 109 * 110 * @param theResourceType The target resource type to search for, e.g. {@literal Patient.class} 111 * @param theMessageHeader The MessageHeader resource to search 112 */ 113 public <T extends IBaseResource> T resolveFirstResourceFromMessageHeaderFocus( 114 Class<T> theResourceType, IBaseResource theMessageHeader) { 115 FhirContext ctx = FhirContext.forCached(theMessageHeader.getStructureFhirVersionEnum()); 116 String type = ctx.getResourceType(theResourceType); 117 List<IBaseReference> focusReferences = 118 ctx.newTerser().getValues(theMessageHeader, "focus", IBaseReference.class, false); 119 IIdType firstId = focusReferences.stream() 120 .map(t -> t.getReferenceElement()) 121 .filter(t -> type.equals(t.getResourceType())) 122 .findFirst() 123 .orElseThrow(); 124 return resolveResource(firstId); 125 } 126 } 127}