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