001/*-
002 * #%L
003 * Smile CDR - CDR
004 * %%
005 * Copyright (C) 2016 - 2025 Smile CDR, Inc.
006 * %%
007 * All rights reserved.
008 * #L%
009 */
010package ca.cdr.api.pub.hl7v2.model;
011
012import ca.cdr.api.model.json.IModelJson;
013import ca.uhn.hl7v2.HL7Exception;
014import ca.uhn.hl7v2.model.AbstractMessage;
015import ca.uhn.hl7v2.model.Message;
016import com.fasterxml.jackson.annotation.JsonCreator;
017import com.fasterxml.jackson.annotation.JsonProperty;
018import com.fasterxml.jackson.annotation.JsonPropertyOrder;
019import io.swagger.v3.oas.annotations.media.Schema;
020import jakarta.annotation.Nonnull;
021import org.hl7.fhir.instance.model.api.IBaseBundle;
022import org.springframework.util.CollectionUtils;
023
024import java.util.ArrayList;
025import java.util.HashMap;
026import java.util.List;
027import java.util.Map;
028
029import static ca.cdr.api.pub.hl7v2.model.Hl7v2ToFhirConversionResultJson.BUNDLES;
030import static ca.cdr.api.pub.hl7v2.model.Hl7v2ToFhirConversionResultJson.DO_AUTO_CONVERT;
031import static ca.cdr.api.pub.hl7v2.model.Hl7v2ToFhirConversionResultJson.DO_PROCESS;
032import static ca.cdr.api.pub.hl7v2.model.Hl7v2ToFhirConversionResultJson.ISSUES;
033import static ca.cdr.api.pub.hl7v2.model.Hl7v2ToFhirConversionResultJson.MODIFIABLE_MESSAGE;
034import static ca.cdr.api.pub.hl7v2.model.Hl7v2ToFhirConversionResultJson.ORIGINAL_MESSAGE;
035import static ca.cdr.api.pub.hl7v2.model.Hl7v2ToFhirConversionResultJson.REQUEST_HEADERS;
036import static java.util.Objects.isNull;
037
038/**
039 * Contains all relevant data involved in the conversion of an HL7 v2.x message to a list of IBaseBundle resources.
040 */
041@JsonPropertyOrder({ORIGINAL_MESSAGE, MODIFIABLE_MESSAGE, BUNDLES, ISSUES, DO_PROCESS, DO_AUTO_CONVERT, REQUEST_HEADERS
042})
043public class Hl7v2ToFhirConversionResultJson implements IModelJson {
044
045        public static final String ORIGINAL_MESSAGE = "originalMessage";
046        public static final String MODIFIABLE_MESSAGE = "modifiableMessage";
047        public static final String BUNDLES = "bundles";
048        public static final String ISSUES = "issues";
049        public static final String DO_PROCESS = "doProcess";
050        public static final String DO_AUTO_CONVERT = "doAutoConvert";
051        public static final String REQUEST_HEADERS = "requestHeaders";
052
053        /**
054         * The original HL7 v2.x message before any customizations have been applied.
055         */
056        @JsonProperty(ORIGINAL_MESSAGE)
057        private Message myOriginalMessage;
058
059        /**
060         * The HL7 v2.x message which customizations can be applied to.
061         */
062        @JsonProperty(MODIFIABLE_MESSAGE)
063        private Message myModifiableMessage;
064
065        /**
066         * A list of Bundle resources that have been created from the modifiableMessage.
067         */
068        @JsonProperty(BUNDLES)
069        private List<IBaseBundle> myBundles = new ArrayList<>();
070
071        /**
072         * A list of issues that have occurred throughout the conversion process.
073         */
074        @JsonProperty(ISSUES)
075        private List<MappingMessage> myIssues = new ArrayList<>();
076
077        /**
078         * A flag to indicate whether a given message should be processed.
079         */
080        @JsonProperty(DO_PROCESS)
081        private boolean myDoProcess;
082
083        /**
084         * A flag to indicate whether a given message should be passed through the Smile generic mapper.
085         * Set this to false in order to skip the Smile generic mapper entirely.
086         */
087        @JsonProperty(DO_AUTO_CONVERT)
088        private boolean myDoAutoConvert;
089
090        @JsonProperty(REQUEST_HEADERS)
091        @Schema(description = "The HTTP request headers associated with the message transmission when applicable")
092        private Map<String, String> myRequestHeaders;
093
094        /**
095         * Constructor
096         * @param theOriginalMessage    The original message to be preserved during the HL7V2 to FHIR conversion
097         */
098        @JsonCreator
099        public Hl7v2ToFhirConversionResultJson(@JsonProperty(ORIGINAL_MESSAGE) Message theOriginalMessage)
100                        throws HL7Exception {
101                myOriginalMessage = theOriginalMessage;
102                myModifiableMessage = cloneMessage(theOriginalMessage);
103                myDoProcess = true;
104                myDoAutoConvert = true;
105        }
106
107        /**
108         * @deprecated  Use {@link AbstractMessage#copy()} instead.
109         * <br/><br/>
110         * Clones the specified {@link Message} into a new {@link Message} with the same contents
111         * @param theMessage            The message to clone
112         */
113        @Deprecated(since = "2023.11.R01")
114        public static Message cloneMessage(Message theMessage) throws HL7Exception {
115                return ((AbstractMessage) theMessage).copy();
116        }
117
118        /**
119         * Adds a bundle
120         * @param theBundle The bundle to add
121         */
122        public void addBundle(IBaseBundle theBundle) {
123                myBundles.add(theBundle);
124        }
125
126        /**
127         * Adds a message to the conversion result. Acceptable message levels are `INFO`, `WARNING`, and `ERROR`
128         *
129         * @param thePath The path within the message where the issue was detected
130         * @param theMessageLevel The issue error level, e.g. 'INFO', 'WARNING', or 'ERROR'.
131         * @param theIssue The description of the issue
132         */
133        public void addIssue(String thePath, MappingMessage.MessageLevel theMessageLevel, String theIssue) {
134                MappingMessage issue = new MappingMessage(thePath, MappingMessage.PathType.HL7V2, theMessageLevel, theIssue);
135                this.addIssue(issue);
136        }
137
138        /**
139         * Adds an issue
140         * @param theIssue      The issue to add
141         */
142        public void addIssue(MappingMessage theIssue) {
143                if (myIssues.isEmpty()) {
144                        myIssues = new ArrayList<>();
145                }
146                myIssues.add(theIssue);
147        }
148
149        /**
150         * Indicates whether there were any issues ({@link MappingMessage.MessageLevel#INFO},
151         * {@link MappingMessage.MessageLevel#WARNING}, or {@link MappingMessage.MessageLevel#ERROR})
152         * during the message conversion
153         * @return true if there are any issues
154         */
155        public boolean hasIssues() {
156                return !CollectionUtils.isEmpty(myIssues);
157        }
158
159        /**
160         * Indicates whether there were any {@link MappingMessage.MessageLevel#INFO} issues
161         * during the message conversion
162         * @return true if there are any {@link MappingMessage.MessageLevel#INFO} issues
163         */
164        public boolean hasInfoIssues() {
165                return hasIssueOfType(MappingMessage.MessageLevel.INFO);
166        }
167
168        /**
169         * Indicates whether there were any {@link MappingMessage.MessageLevel#WARNING} issues
170         * during the message conversion
171         * @return true if there are any {@link MappingMessage.MessageLevel#WARNING} issues
172         */
173        public boolean hasWarningIssues() {
174                return hasIssueOfType(MappingMessage.MessageLevel.WARNING);
175        }
176
177        /**
178         * Indicates whether there were any {@link MappingMessage.MessageLevel#ERROR} issues
179         * during the message conversion
180         * @return true if there are any {@link MappingMessage.MessageLevel#ERROR} issues
181         */
182        public boolean hasErrorIssues() {
183                return hasIssueOfType(MappingMessage.MessageLevel.ERROR);
184        }
185
186        private boolean hasIssueOfType(MappingMessage.MessageLevel theType) {
187                return myIssues.stream().anyMatch(m -> theType.equals(m.getLevel()));
188        }
189
190        /**
191         * @return              A copy of the original HL7 v2.x message before any customizations have been applied.
192         */
193        public Message getOriginalMessage() throws HL7Exception {
194                return cloneMessage(myOriginalMessage);
195        }
196
197        /**
198         * @return              The HL7 v2.x message which customizations can be applied to.
199         */
200        public Message getModifiableMessage() {
201                return myModifiableMessage;
202        }
203
204        /**
205         * Sets the HL7 v2.x message which customizations can be applied to.
206         * @param theModifiableMessage          The modifiable Message
207         */
208        public void setModifiableMessage(Message theModifiableMessage) {
209                myModifiableMessage = theModifiableMessage;
210        }
211
212        /**
213         * @return A list of Bundle resources that have been created from the modifiable Message.
214         */
215        public List<IBaseBundle> getBundles() {
216                return myBundles;
217        }
218
219        /**
220         * Sets the list of Bundle resources that have been created from the modifiable Message.
221         * @param theBundles                            The Bundles
222         */
223        public void setBundles(List<IBaseBundle> theBundles) {
224                myBundles = theBundles;
225        }
226
227        /**
228         * @return A list of issues that have occurred throughout the conversion process.
229         */
230        public List<MappingMessage> getIssues() {
231                return myIssues;
232        }
233
234        /**
235         * Sets the list of issues that have occurred throughout the conversion process.
236         * @param theIssues             The issues
237         */
238        public void setIssues(List<MappingMessage> theIssues) {
239                myIssues = new ArrayList<>();
240                addIssues(theIssues);
241        }
242
243        /**
244         * Adds the items in list of issues that have occurred throughout the conversion process.
245         * @param theIssues             The issues
246         */
247        public void addIssues(List<MappingMessage> theIssues) {
248                if (!theIssues.isEmpty()) {
249                        myIssues.addAll(theIssues);
250                }
251        }
252
253        /**
254         * @return              Whether the modifiable Message should be processed.
255         */
256        public boolean isDoProcess() {
257                return myDoProcess;
258        }
259
260        /**
261         * Sets a flag to indicate whether the modifiable Message should be processed.
262         * @param theDoProcess          The value of the flag
263         */
264        public void setDoProcess(boolean theDoProcess) {
265                myDoProcess = theDoProcess;
266        }
267
268        /**
269         * @return Whether the modifiable Message should be passed through the Smile generic mapper.
270         */
271        public boolean isDoAutoConvert() {
272                return myDoAutoConvert;
273        }
274
275        /**
276         * Sets a flag to indicate whether the modifiable Message should be passed through the Smile generic mapper.
277         * <br/>
278         * The Smile generic mapper will be skipped entirely if this flag is set to false.
279         * @param theDoAutoConvert              The value of the flag
280         */
281        public void setDoAutoConvert(boolean theDoAutoConvert) {
282                myDoAutoConvert = theDoAutoConvert;
283        }
284
285        /**
286         *
287         * @return the http request headers associated with the request (if any).
288         */
289        @Nonnull
290        public Map<String, String> getRequestHeaders() {
291                if (isNull(myRequestHeaders)) {
292                        myRequestHeaders = new HashMap<>();
293                }
294
295                return myRequestHeaders;
296        }
297
298        /**
299         * Stores the request headers associated with the request (if any)
300         * @param theRequestHeaders
301         * @return
302         */
303        public void setRequestHeaders(Map<String, String> theRequestHeaders) {
304                myRequestHeaders = theRequestHeaders;
305        }
306}