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