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}