001package ca.cdr.api.model.json;
002
003/*
004 * #%L
005 * Smile CDR - CDR
006 * %%
007 * Copyright (C) 2016 - 2026 Smile CDR, Inc.
008 * %%
009 * All rights reserved.
010 * #L%
011 */
012
013import ca.uhn.fhir.context.FhirContext;
014import ca.uhn.fhir.jpa.dao.TransactionUtil;
015import ca.uhn.fhir.model.api.IModelJson;
016import ca.uhn.fhir.model.api.StorageResponseCodeEnum;
017import ca.uhn.fhir.model.primitive.IdDt;
018import ca.uhn.fhir.util.FhirTerser;
019import com.fasterxml.jackson.annotation.JsonProperty;
020import jakarta.annotation.Nonnull;
021import jakarta.annotation.Nullable;
022import org.apache.commons.lang3.builder.CompareToBuilder;
023import org.hl7.fhir.instance.model.api.IBase;
024import org.hl7.fhir.instance.model.api.IBaseBundle;
025import org.hl7.fhir.instance.model.api.IBaseResource;
026import org.hl7.fhir.instance.model.api.IIdType;
027import org.slf4j.Logger;
028import org.slf4j.LoggerFactory;
029
030import java.util.ArrayList;
031import java.util.List;
032
033import static org.apache.commons.lang3.StringUtils.isBlank;
034import static org.apache.commons.lang3.StringUtils.isNotBlank;
035
036public class TransactionLogStorageOutcomeJson
037                implements IModelJson, Comparable<TransactionLogStorageOutcomeJson>, Cloneable {
038        private static final Logger ourLog = LoggerFactory.getLogger(TransactionLogStorageOutcomeJson.class);
039
040        @JsonProperty("sourceUri")
041        private String mySourceUri;
042
043        @JsonProperty("sourceResourceType")
044        private String mySourceResourceType;
045
046        @JsonProperty("sourceResourceId")
047        private String mySourceResourceId;
048
049        @JsonProperty("targetResourceType")
050        private String myTargetResourceType;
051
052        @JsonProperty("targetResourceId")
053        private String myTargetResourceId;
054
055        @JsonProperty("targetResourceVersion")
056        private Long myTargetResourceVersion;
057
058        @JsonProperty("outcome")
059        private StorageResponseCodeEnum myOutcome;
060
061        @JsonProperty("errorMessage")
062        private String myErrorMessage;
063
064        public Long getTargetResourceVersion() {
065                return myTargetResourceVersion;
066        }
067
068        public TransactionLogStorageOutcomeJson setTargetResourceVersion(Long theTargetResourceVersion) {
069                myTargetResourceVersion = theTargetResourceVersion;
070                return this;
071        }
072
073        public String getErrorMessage() {
074                return myErrorMessage;
075        }
076
077        public TransactionLogStorageOutcomeJson setErrorMessage(String theErrorMessage) {
078                myErrorMessage = theErrorMessage;
079                return this;
080        }
081
082        public String getSourceUri() {
083                return mySourceUri;
084        }
085
086        public TransactionLogStorageOutcomeJson setSourceUri(String theSourceUri) {
087                mySourceUri = theSourceUri;
088                return this;
089        }
090
091        public String getTargetResourceType() {
092                return myTargetResourceType;
093        }
094
095        public TransactionLogStorageOutcomeJson setTargetResourceType(String theTargetResourceType) {
096                myTargetResourceType = theTargetResourceType;
097                return this;
098        }
099
100        public String getTargetResourceId() {
101                return myTargetResourceId;
102        }
103
104        public IIdType toTargetResourceIdType() {
105                String targetVersion = myTargetResourceVersion != null ? myTargetResourceVersion.toString() : null;
106                return new IdDt(myTargetResourceType, myTargetResourceId, targetVersion);
107        }
108
109        public TransactionLogStorageOutcomeJson setTargetResourceId(String theTargetResourceId) {
110                myTargetResourceId = theTargetResourceId;
111                return this;
112        }
113
114        public StorageResponseCodeEnum getOutcome() {
115                return myOutcome;
116        }
117
118        public TransactionLogStorageOutcomeJson setOutcome(StorageResponseCodeEnum theOutcome) {
119                myOutcome = theOutcome;
120                return this;
121        }
122
123        @Override
124        public int compareTo(@Nonnull TransactionLogStorageOutcomeJson o) {
125                return new CompareToBuilder()
126                                .append(mySourceUri, o.mySourceUri)
127                                .append(myTargetResourceType, o.myTargetResourceType)
128                                .append(myTargetResourceId, o.myTargetResourceId)
129                                .append(myOutcome, o.myOutcome)
130                                .append(myErrorMessage, o.myErrorMessage)
131                                .toComparison();
132        }
133
134        public String getSourceResourceType() {
135                return mySourceResourceType;
136        }
137
138        public TransactionLogStorageOutcomeJson setSourceResourceType(String theSourceResourceType) {
139                mySourceResourceType = theSourceResourceType;
140                return this;
141        }
142
143        public String getSourceResourceId() {
144                return mySourceResourceId;
145        }
146
147        public TransactionLogStorageOutcomeJson setSourceResourceId(String theSourceResourceId) {
148                mySourceResourceId = theSourceResourceId;
149                return this;
150        }
151
152        public IIdType toSourceResourceIdType() {
153                return new IdDt(mySourceResourceType, mySourceResourceId);
154        }
155
156        public static TransactionLogStorageOutcomeJson fromStorageOutcome(
157                        TransactionUtil.StorageOutcome theStorageOutcome) {
158
159                String sourceUri = theStorageOutcome.getRequestMetaSource();
160
161                boolean isFailure = theStorageOutcome.getStatusCode() >= 400 && theStorageOutcome.getStatusCode() <= 599;
162                String errorMessage = theStorageOutcome.getErrorMessage();
163                if (isBlank(errorMessage) && isFailure) {
164                        errorMessage = theStorageOutcome.getStatusMessage();
165                }
166
167                StorageResponseCodeEnum responseCode = theStorageOutcome.getStorageResponseCode();
168                IIdType sourceId = theStorageOutcome.getSourceId();
169                IIdType targetId = theStorageOutcome.getTargetId();
170
171                TransactionLogStorageOutcomeJson logStorageOutcome = new TransactionLogStorageOutcomeJson();
172                logStorageOutcome.setOutcome(responseCode);
173                logStorageOutcome.setErrorMessage(errorMessage);
174                if (targetId != null) {
175                        logStorageOutcome.setTargetResourceType(targetId.getResourceType());
176                        logStorageOutcome.setTargetResourceId(targetId.getIdPart());
177                        if (targetId.isVersionIdPartValidLong()) {
178                                logStorageOutcome.setTargetResourceVersion(targetId.getVersionIdPartAsLong());
179                        }
180                }
181                if (sourceId != null) {
182                        logStorageOutcome.setSourceResourceType(sourceId.getResourceType());
183                        logStorageOutcome.setSourceResourceId(sourceId.getIdPart());
184                }
185                logStorageOutcome.setSourceUri(sourceUri);
186                return logStorageOutcome;
187        }
188
189        public static List<TransactionLogStorageOutcomeJson> processResponseBundleForStorageOutcomes(
190                        @Nonnull FhirContext theFhirContext,
191                        @Nullable IBaseBundle theRequest,
192                        @Nullable IBaseBundle theResponse,
193                        String theGlobalErrorMessage) {
194                List<TransactionLogStorageOutcomeJson> retVal = new ArrayList<>();
195
196                FhirTerser terser = theFhirContext.newTerser();
197
198                if (isNotBlank(theGlobalErrorMessage)) {
199                        String requestBundleSource = terser.getSinglePrimitiveValueOrNull(theRequest, "Bundle.meta.source");
200                        List<IBase> requestEntries = terser.getValues(theRequest, "entry");
201                        int count = requestEntries.size();
202
203                        for (int i = 0; i < count; i++) {
204                                IBase requestEntry = requestEntries.get(i);
205                                IIdType targetId = terser.getSingleValue(requestEntry, "resource", IBaseResource.class)
206                                                .map(IBaseResource::getIdElement)
207                                                .orElse(null);
208                                if (targetId != null) {
209                                        TransactionLogStorageOutcomeJson storageOutcome = new TransactionLogStorageOutcomeJson();
210                                        storageOutcome.setSourceUri(requestBundleSource);
211                                        storageOutcome.setOutcome(StorageResponseCodeEnum.FAILURE);
212                                        storageOutcome.setErrorMessage(theGlobalErrorMessage);
213                                        storageOutcome.setTargetResourceType(targetId.getResourceType());
214                                        storageOutcome.setTargetResourceId(targetId.getIdPart());
215                                        retVal.add(storageOutcome);
216                                }
217                        }
218                        return retVal;
219                }
220
221                if (theResponse == null) {
222                        // This shouldn't happen
223                        ourLog.warn("No response Bundle provided but no error message was present");
224                        return retVal;
225                }
226
227                TransactionUtil.TransactionResponse transactionResponse =
228                                TransactionUtil.parseTransactionResponse(theFhirContext, theRequest, theResponse);
229                for (TransactionUtil.StorageOutcome storageOutcome : transactionResponse.getStorageOutcomes()) {
230                        TransactionLogStorageOutcomeJson logStorageOutcome = fromStorageOutcome(storageOutcome);
231                        retVal.add(logStorageOutcome);
232                }
233
234                return retVal;
235        }
236
237        @Override
238        public TransactionLogStorageOutcomeJson clone() {
239                try {
240                        return (TransactionLogStorageOutcomeJson) super.clone();
241                } catch (CloneNotSupportedException e) {
242                        throw new UnsupportedOperationException();
243                }
244        }
245}