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.Objects; 035import java.util.Optional; 036 037import static org.apache.commons.lang3.StringUtils.defaultString; 038 039@SuppressWarnings("WeakerAccess") 040public abstract class BaseResourceMessage implements IResourceMessage, IModelJson { 041 042 @JsonProperty("operationType") 043 protected BaseResourceModifiedMessage.OperationTypeEnum myOperationType; 044 045 @JsonProperty("attributes") 046 private Map<String, String> myAttributes; 047 048 @JsonProperty("transactionId") 049 private String myTransactionId; 050 051 @JsonProperty("mediaType") 052 private String myMediaType; 053 054 /** 055 * This is used by any message going to kafka for topic partition selection purposes. 056 */ 057 @JsonProperty("messageKey") 058 private String myMessageKey; 059 060 /** 061 * Returns an attribute stored in this message. 062 * <p> 063 * Attributes are just a spot for user data of any kind to be 064 * added to the message for pasing along the subscription processing 065 * pipeline (typically by interceptors). Values will be carried from the beginning to the end. 066 * </p> 067 * <p> 068 * Note that messages are designed to be passed into queueing systems 069 * and serialized as JSON. As a result, only strings are currently allowed 070 * as values. 071 * </p> 072 */ 073 public Optional<String> getAttribute(String theKey) { 074 Validate.notBlank(theKey); 075 if (myAttributes == null) { 076 return Optional.empty(); 077 } 078 return Optional.ofNullable(myAttributes.get(theKey)); 079 } 080 081 /** 082 * Sets an attribute stored in this message. 083 * <p> 084 * Attributes are just a spot for user data of any kind to be 085 * added to the message for passing along the subscription processing 086 * pipeline (typically by interceptors). Values will be carried from the beginning to the end. 087 * </p> 088 * <p> 089 * Note that messages are designed to be passed into queueing systems 090 * and serialized as JSON. As a result, only strings are currently allowed 091 * as values. 092 * </p> 093 * 094 * @param theKey The key (must not be null or blank) 095 * @param theValue The value (must not be null) 096 */ 097 public void setAttribute(String theKey, String theValue) { 098 Validate.notBlank(theKey); 099 Validate.notNull(theValue); 100 if (myAttributes == null) { 101 myAttributes = new HashMap<>(); 102 } 103 myAttributes.put(theKey, theValue); 104 } 105 106 /** 107 * Copies any attributes from the given message into this messsage. 108 * 109 * @see #setAttribute(String, String) 110 * @see #getAttribute(String) 111 */ 112 public void copyAdditionalPropertiesFrom(BaseResourceMessage theMsg) { 113 if (theMsg.myAttributes != null) { 114 if (myAttributes == null) { 115 myAttributes = new HashMap<>(); 116 } 117 myAttributes.putAll(theMsg.myAttributes); 118 } 119 } 120 121 /** 122 * Returns the {@link OperationTypeEnum} that is occurring to the Resource of the message 123 * 124 * @return the operation type. 125 */ 126 public BaseResourceModifiedMessage.OperationTypeEnum getOperationType() { 127 return myOperationType; 128 } 129 130 /** 131 * Sets the {@link OperationTypeEnum} occuring to the resource of the message. 132 * 133 * @param theOperationType The operation type to set. 134 */ 135 public void setOperationType(BaseResourceModifiedMessage.OperationTypeEnum theOperationType) { 136 myOperationType = theOperationType; 137 } 138 139 /** 140 * Retrieve the transaction ID related to this message. 141 * 142 * @return the transaction ID, or null. 143 */ 144 @Nullable 145 public String getTransactionId() { 146 return myTransactionId; 147 } 148 149 /** 150 * Adds a transaction ID to this message. This ID can be used for many purposes. For example, performing tracing 151 * across asynchronous hooks, tying data together, or downstream logging purposes. 152 * <p> 153 * One current internal implementation uses this field to tie back MDM processing results (which are asynchronous) 154 * to the original transaction log that caused the MDM processing to occur. 155 * 156 * @param theTransactionId An ID representing a transaction of relevance to this message. 157 */ 158 public void setTransactionId(String theTransactionId) { 159 myTransactionId = theTransactionId; 160 } 161 162 public String getMediaType() { 163 return myMediaType; 164 } 165 166 public void setMediaType(String theMediaType) { 167 myMediaType = theMediaType; 168 } 169 170 @Deprecated 171 @Nullable 172 public String getMessageKeyOrNull() { 173 return getMessageKey(); 174 } 175 176 @Nullable 177 public String getMessageKey() { 178 return myMessageKey; 179 } 180 181 public void setMessageKey(String theMessageKey) { 182 myMessageKey = theMessageKey; 183 } 184 185 /** 186 * Returns {@link #getMessageKey()} or {@link #getMessageKeyDefaultValue()} when {@link #getMessageKey()} returns <code>null</code>. 187 * 188 * @return the message key value or default 189 */ 190 @Nullable 191 public String getMessageKeyOrDefault() { 192 return defaultString(getMessageKey(), getMessageKeyDefaultValue()); 193 } 194 195 /** 196 * Provides a fallback value when method {@link #getMessageKey()} returns <code>null</code>. 197 * 198 * @return null by default 199 */ 200 @Nullable 201 protected String getMessageKeyDefaultValue() { 202 return null; 203 } 204 205 public enum OperationTypeEnum { 206 CREATE(RestOperationTypeEnum.CREATE), 207 UPDATE(RestOperationTypeEnum.UPDATE), 208 DELETE(RestOperationTypeEnum.DELETE), 209 MANUALLY_TRIGGERED(RestOperationTypeEnum.UPDATE), 210 TRANSACTION(RestOperationTypeEnum.UPDATE); 211 212 private final RestOperationTypeEnum myRestOperationTypeEnum; 213 214 OperationTypeEnum(RestOperationTypeEnum theRestOperationTypeEnum) { 215 myRestOperationTypeEnum = theRestOperationTypeEnum; 216 } 217 218 public static OperationTypeEnum from(RestOperationTypeEnum theRestOperationType) { 219 switch (theRestOperationType) { 220 case CREATE: 221 return CREATE; 222 case UPDATE: 223 return UPDATE; 224 case DELETE: 225 return DELETE; 226 default: 227 throw new IllegalArgumentException( 228 Msg.code(2348) + "Unsupported operation type: " + theRestOperationType); 229 } 230 } 231 232 public RestOperationTypeEnum asRestOperationType() { 233 return myRestOperationTypeEnum; 234 } 235 } 236 237 @VisibleForTesting 238 public Map<String, String> getAttributes() { 239 return ObjectUtils.defaultIfNull(myAttributes, Collections.emptyMap()); 240 } 241 242 @Override 243 public boolean equals(Object theO) { 244 if (this == theO) return true; 245 if (theO == null || getClass() != theO.getClass()) return false; 246 BaseResourceMessage that = (BaseResourceMessage) theO; 247 return getOperationType() == that.getOperationType() 248 && Objects.equals(getAttributes(), that.getAttributes()) 249 && Objects.equals(getTransactionId(), that.getTransactionId()) 250 && Objects.equals(getMediaType(), that.getMediaType()) 251 && Objects.equals(getMessageKey(), that.getMessageKey()); 252 } 253 254 @Override 255 public int hashCode() { 256 return Objects.hash(getOperationType(), getAttributes(), getTransactionId(), getMediaType()); 257 } 258}