001/*- 002 * #%L 003 * HAPI FHIR - Server Framework 004 * %% 005 * Copyright (C) 2014 - 2024 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.rest.server.messaging; 021 022import ca.uhn.fhir.i18n.Msg; 023import ca.uhn.fhir.model.api.IModelJson; 024import ca.uhn.fhir.rest.api.RestOperationTypeEnum; 025import com.fasterxml.jackson.annotation.JsonProperty; 026import com.google.common.annotations.VisibleForTesting; 027import jakarta.annotation.Nullable; 028import org.apache.commons.lang3.ObjectUtils; 029import org.apache.commons.lang3.Validate; 030 031import java.util.Collections; 032import java.util.HashMap; 033import java.util.Map; 034import java.util.Optional; 035 036import static org.apache.commons.lang3.StringUtils.defaultString; 037 038@SuppressWarnings("WeakerAccess") 039public abstract class BaseResourceMessage implements IResourceMessage, IModelJson { 040 041 @JsonProperty("operationType") 042 protected BaseResourceModifiedMessage.OperationTypeEnum myOperationType; 043 044 @JsonProperty("attributes") 045 private Map<String, String> myAttributes; 046 047 @JsonProperty("transactionId") 048 private String myTransactionId; 049 050 @JsonProperty("mediaType") 051 private String myMediaType; 052 053 /** 054 * This is used by any message going to kafka for topic partition selection purposes. 055 */ 056 @JsonProperty("messageKey") 057 private String myMessageKey; 058 059 /** 060 * Returns an attribute stored in this message. 061 * <p> 062 * Attributes are just a spot for user data of any kind to be 063 * added to the message for pasing along the subscription processing 064 * pipeline (typically by interceptors). Values will be carried from the beginning to the end. 065 * </p> 066 * <p> 067 * Note that messages are designed to be passed into queueing systems 068 * and serialized as JSON. As a result, only strings are currently allowed 069 * as values. 070 * </p> 071 */ 072 public Optional<String> getAttribute(String theKey) { 073 Validate.notBlank(theKey); 074 if (myAttributes == null) { 075 return Optional.empty(); 076 } 077 return Optional.ofNullable(myAttributes.get(theKey)); 078 } 079 080 /** 081 * Sets an attribute stored in this message. 082 * <p> 083 * Attributes are just a spot for user data of any kind to be 084 * added to the message for passing along the subscription processing 085 * pipeline (typically by interceptors). Values will be carried from the beginning to the end. 086 * </p> 087 * <p> 088 * Note that messages are designed to be passed into queueing systems 089 * and serialized as JSON. As a result, only strings are currently allowed 090 * as values. 091 * </p> 092 * 093 * @param theKey The key (must not be null or blank) 094 * @param theValue The value (must not be null) 095 */ 096 public void setAttribute(String theKey, String theValue) { 097 Validate.notBlank(theKey); 098 Validate.notNull(theValue); 099 if (myAttributes == null) { 100 myAttributes = new HashMap<>(); 101 } 102 myAttributes.put(theKey, theValue); 103 } 104 105 /** 106 * Copies any attributes from the given message into this messsage. 107 * 108 * @see #setAttribute(String, String) 109 * @see #getAttribute(String) 110 */ 111 public void copyAdditionalPropertiesFrom(BaseResourceMessage theMsg) { 112 if (theMsg.myAttributes != null) { 113 if (myAttributes == null) { 114 myAttributes = new HashMap<>(); 115 } 116 myAttributes.putAll(theMsg.myAttributes); 117 } 118 } 119 120 /** 121 * Returns the {@link OperationTypeEnum} that is occurring to the Resource of the message 122 * 123 * @return the operation type. 124 */ 125 public BaseResourceModifiedMessage.OperationTypeEnum getOperationType() { 126 return myOperationType; 127 } 128 129 /** 130 * Sets the {@link OperationTypeEnum} occuring to the resource of the message. 131 * 132 * @param theOperationType The operation type to set. 133 */ 134 public void setOperationType(BaseResourceModifiedMessage.OperationTypeEnum theOperationType) { 135 myOperationType = theOperationType; 136 } 137 138 /** 139 * Retrieve the transaction ID related to this message. 140 * 141 * @return the transaction ID, or null. 142 */ 143 @Nullable 144 public String getTransactionId() { 145 return myTransactionId; 146 } 147 148 /** 149 * Adds a transaction ID to this message. This ID can be used for many purposes. For example, performing tracing 150 * across asynchronous hooks, tying data together, or downstream logging purposes. 151 * <p> 152 * One current internal implementation uses this field to tie back MDM processing results (which are asynchronous) 153 * to the original transaction log that caused the MDM processing to occur. 154 * 155 * @param theTransactionId An ID representing a transaction of relevance to this message. 156 */ 157 public void setTransactionId(String theTransactionId) { 158 myTransactionId = theTransactionId; 159 } 160 161 public String getMediaType() { 162 return myMediaType; 163 } 164 165 public void setMediaType(String theMediaType) { 166 myMediaType = theMediaType; 167 } 168 169 @Deprecated 170 @Nullable 171 public String getMessageKeyOrNull() { 172 return getMessageKey(); 173 } 174 175 @Nullable 176 public String getMessageKey() { 177 return myMessageKey; 178 } 179 180 public void setMessageKey(String theMessageKey) { 181 myMessageKey = theMessageKey; 182 } 183 184 /** 185 * Returns {@link #getMessageKey()} or {@link #getMessageKeyDefaultValue()} when {@link #getMessageKey()} returns <code>null</code>. 186 * 187 * @return the message key value or default 188 */ 189 @Nullable 190 public String getMessageKeyOrDefault() { 191 return defaultString(getMessageKey(), getMessageKeyDefaultValue()); 192 } 193 194 /** 195 * Provides a fallback value when method {@link #getMessageKey()} returns <code>null</code>. 196 * 197 * @return null by default 198 */ 199 @Nullable 200 protected String getMessageKeyDefaultValue() { 201 return null; 202 } 203 204 public enum OperationTypeEnum { 205 CREATE(RestOperationTypeEnum.CREATE), 206 UPDATE(RestOperationTypeEnum.UPDATE), 207 DELETE(RestOperationTypeEnum.DELETE), 208 MANUALLY_TRIGGERED(RestOperationTypeEnum.UPDATE), 209 TRANSACTION(RestOperationTypeEnum.UPDATE); 210 211 private final RestOperationTypeEnum myRestOperationTypeEnum; 212 213 OperationTypeEnum(RestOperationTypeEnum theRestOperationTypeEnum) { 214 myRestOperationTypeEnum = theRestOperationTypeEnum; 215 } 216 217 public static OperationTypeEnum from(RestOperationTypeEnum theRestOperationType) { 218 switch (theRestOperationType) { 219 case CREATE: 220 return CREATE; 221 case UPDATE: 222 return UPDATE; 223 case DELETE: 224 return DELETE; 225 default: 226 throw new IllegalArgumentException( 227 Msg.code(2348) + "Unsupported operation type: " + theRestOperationType); 228 } 229 } 230 231 public RestOperationTypeEnum asRestOperationType() { 232 return myRestOperationTypeEnum; 233 } 234 } 235 236 @VisibleForTesting 237 public Map<String, String> getAttributes() { 238 return ObjectUtils.defaultIfNull(myAttributes, Collections.emptyMap()); 239 } 240}