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.util;
011
012import ca.cdr.api.model.json.TransactionLogStepJson;
013import ca.cdr.api.pub.hl7v2.model.MappingMessage;
014import ca.uhn.fhir.rest.api.server.RequestDetails;
015import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
016import com.fasterxml.jackson.core.JsonProcessingException;
017import com.fasterxml.jackson.databind.ObjectMapper;
018import org.apache.commons.lang3.Validate;
019
020import java.util.ArrayList;
021import java.util.Collections;
022import java.util.HashMap;
023import java.util.List;
024import java.util.Map;
025
026/**
027 * NOTE: this utility is using vanilla Jackson - FHIR resources can not be serialized.
028 */
029public class TransactionLogRequestDetailsUtil {
030        public static final String STEPS_LIST_KEY = TransactionLogRequestDetailsUtil.class.getName() + "_STEPS_LIST";
031        public static final String REQUEST_SUBTYPE_KEY =
032                        TransactionLogRequestDetailsUtil.class.getName() + "_REQUEST_SUBTYPE_KEY";
033        private static final String MESSAGES_LIST_KEY = TransactionLogRequestDetailsUtil.class.getName() + "_MESSAGES_LIST";
034        private static final String ADDITIONAL_JSON_MAP_KEY =
035                        TransactionLogRequestDetailsUtil.class.getName() + "_ADDITIONAL_JSON_MAP";
036
037        private static final ObjectMapper ourObjectMapper = new ObjectMapper();
038
039        private TransactionLogRequestDetailsUtil() {}
040
041        public static void addMessageToRequest(RequestDetails theDetails, MappingMessage theMessage) {
042                Validate.notNull(theDetails, "theDetails must not be null");
043                Validate.notNull(theMessage, "theMessage must not be null");
044                @SuppressWarnings("unchecked")
045                List<MappingMessage> messages =
046                                (List<MappingMessage>) theDetails.getUserData().get(MESSAGES_LIST_KEY);
047                if (messages == null) {
048                        messages = new ArrayList<>();
049                        theDetails.getUserData().put(MESSAGES_LIST_KEY, messages);
050                }
051                messages.add(theMessage);
052        }
053
054        public static List<MappingMessage> getMessagesOnRequest(RequestDetails theDetails) {
055                @SuppressWarnings("unchecked")
056                List<MappingMessage> messages =
057                                (List<MappingMessage>) theDetails.getUserData().get(MESSAGES_LIST_KEY);
058                if (messages == null) {
059                        return List.of();
060                }
061                return Collections.unmodifiableList(messages);
062        }
063
064        public static List<TransactionLogStepJson> getTransactionLogStepsFromRequest(RequestDetails theRequestDetails) {
065                @SuppressWarnings("unchecked")
066                List<TransactionLogStepJson> steps =
067                                (List<TransactionLogStepJson>) theRequestDetails.getUserData().get(STEPS_LIST_KEY);
068                if (steps == null) {
069                        return List.of();
070                }
071                return Collections.unmodifiableList(steps);
072        }
073
074        /**
075         * Adds a transaction log step to the attribute which holds the list of steps for the provided request.
076         * @param theRequest the request
077         * @param theStep the transaction log step to add
078         */
079        public static void addTransactionLogStepForRequest(RequestDetails theRequest, TransactionLogStepJson theStep) {
080                Validate.notNull(theRequest, "theRequest must not be null");
081                Validate.notNull(theStep, "theStep must not be null");
082                @SuppressWarnings("unchecked")
083                List<TransactionLogStepJson> steps =
084                                (List<TransactionLogStepJson>) theRequest.getUserData().get(STEPS_LIST_KEY);
085                if (steps == null) {
086                        steps = new ArrayList<>();
087                        theRequest.getUserData().put(STEPS_LIST_KEY, steps);
088                }
089                steps.add(theStep);
090        }
091
092        /**
093         * NOTE: this method is using vanilla Jackson - FHIR resources can not be serialized.
094         *
095         * Utility method to add key/value pairs to a transactionLog.  The provided value needs to be serializable
096         * with Jackson since pairs are accumulated and subsequently formatted in an inline json string.
097         * IllegalArgumentException is thrown if <code>theValue</code> is not Json serializable with Jackson or <code>null</code>.
098         *      The additional log properties will be tallied and rendered as inline json in the transactionLogEventJson:
099         *{
100         *              "id": 2,
101         *      "initialTimestamp": "2023-08-29T14:00:30.096-04:00",
102         *              "type": "FHIR_REQUEST",
103         *              "subType": "FHIR_CREATE",
104         *              "outcome": "SUCCESS",
105         *              ...
106         *              "requestId": "r1bW96uhRAn2z8iU",
107         *              "additionalJson": { "diseases": [ "Tuberculosis", "Polio", "Malaria", "Dengue fever"], "clinic": "Acme Ouest" },
108         *              ...
109         *              "userFamilyName": "GenericUser",
110         *              "userGivenName": "Admin"
111         *              }
112         *
113         * @param theRequestDetails The request details where the pairs will be stored
114         * @param theKey The key for referencing to theObject.  Keys are transformed into json properties when serializing a pair.
115         * @param theValue The value needing storage.  Values are transformed into json values when serializing a pair.
116         *
117         */
118        public static void addAdditionalJsonProperty(RequestDetails theRequestDetails, String theKey, Object theValue) {
119                Validate.notNull(theRequestDetails, "theDetails must not be null");
120                Validate.notNull(theKey, "theKey must not be null");
121                Validate.notNull(theValue, "theValue must not be null");
122
123                try {
124                        // ensure that the value can be serialized to JSON
125                        ourObjectMapper.writeValueAsString(theValue);
126                } catch (JsonProcessingException e) {
127                        throw new IllegalArgumentException(
128                                        String.format(
129                                                        "Unable to add pair with key %s to the additional Json property as the provided value object could not be parsed.",
130                                                        theKey),
131                                        e);
132                }
133                @SuppressWarnings("unchecked")
134                Map<String, Object> additionalJsonPropertiesMap =
135                                (Map<String, Object>) theRequestDetails.getUserData().get(ADDITIONAL_JSON_MAP_KEY);
136                if (additionalJsonPropertiesMap == null) {
137                        additionalJsonPropertiesMap = new HashMap<>();
138                        theRequestDetails.getUserData().put(ADDITIONAL_JSON_MAP_KEY, additionalJsonPropertiesMap);
139                }
140                additionalJsonPropertiesMap.put(theKey, theValue);
141        }
142
143        public static Map<String, Object> getAdditionalJsonPropertiesMap(RequestDetails theRequestDetails) {
144                Validate.notNull(theRequestDetails, "theRequestDetails must not be null");
145                @SuppressWarnings("unchecked")
146                Map<String, Object> additionalJsonPropertiesMap =
147                                (Map<String, Object>) theRequestDetails.getUserData().get(ADDITIONAL_JSON_MAP_KEY);
148                if (additionalJsonPropertiesMap == null) {
149                        return Map.of();
150                }
151                return additionalJsonPropertiesMap;
152        }
153
154        public static boolean hasTransactionLogSteps(ServletRequestDetails theRequestDetails) {
155                return theRequestDetails.getUserData().containsKey(STEPS_LIST_KEY);
156        }
157}