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