
001/*- 002 * #%L 003 * HAPI FHIR - Server Framework 004 * %% 005 * Copyright (C) 2014 - 2025 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.interceptor.model.RequestPartitionId; 024import ca.uhn.fhir.model.api.IModelJson; 025import ca.uhn.fhir.rest.api.RestOperationTypeEnum; 026import com.fasterxml.jackson.annotation.JsonProperty; 027import com.google.common.annotations.VisibleForTesting; 028import jakarta.annotation.Nullable; 029import org.apache.commons.lang3.ObjectUtils; 030import org.apache.commons.lang3.Validate; 031 032import java.util.Collections; 033import java.util.HashMap; 034import java.util.Map; 035import java.util.Objects; 036import java.util.Optional; 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 @JsonProperty("messageKey") 054 private String myPayloadMessageKey; 055 056 public abstract RequestPartitionId getPartitionId(); 057 058 public abstract void setPartitionId(RequestPartitionId thePartitionId); 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 Objects.requireNonNull(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 @Override 171 @Nullable 172 public String getPayloadMessageKey() { 173 return myPayloadMessageKey; 174 } 175 176 @Override 177 public void setPayloadMessageKey(String thePayloadMessageKey) { 178 myPayloadMessageKey = thePayloadMessageKey; 179 } 180 181 public enum OperationTypeEnum { 182 CREATE(RestOperationTypeEnum.CREATE), 183 UPDATE(RestOperationTypeEnum.UPDATE), 184 DELETE(RestOperationTypeEnum.DELETE), 185 MANUALLY_TRIGGERED(RestOperationTypeEnum.UPDATE), 186 TRANSACTION(RestOperationTypeEnum.UPDATE); 187 188 private final RestOperationTypeEnum myRestOperationTypeEnum; 189 190 OperationTypeEnum(RestOperationTypeEnum theRestOperationTypeEnum) { 191 myRestOperationTypeEnum = theRestOperationTypeEnum; 192 } 193 194 public static OperationTypeEnum from(RestOperationTypeEnum theRestOperationType) { 195 switch (theRestOperationType) { 196 case CREATE: 197 return CREATE; 198 case UPDATE: 199 return UPDATE; 200 case DELETE: 201 return DELETE; 202 default: 203 throw new IllegalArgumentException( 204 Msg.code(2348) + "Unsupported operation type: " + theRestOperationType); 205 } 206 } 207 208 public RestOperationTypeEnum asRestOperationType() { 209 return myRestOperationTypeEnum; 210 } 211 } 212 213 @VisibleForTesting 214 public Map<String, String> getAttributes() { 215 return ObjectUtils.defaultIfNull(myAttributes, Collections.emptyMap()); 216 } 217 218 @Override 219 public boolean equals(Object theO) { 220 if (this == theO) return true; 221 if (theO == null || getClass() != theO.getClass()) return false; 222 BaseResourceMessage that = (BaseResourceMessage) theO; 223 return getOperationType() == that.getOperationType() 224 && Objects.equals(getAttributes(), that.getAttributes()) 225 && Objects.equals(getTransactionId(), that.getTransactionId()) 226 && Objects.equals(getMediaType(), that.getMediaType()) 227 && Objects.equals(getPayloadMessageKey(), that.getPayloadMessageKey()); 228 } 229 230 @Override 231 public int hashCode() { 232 return Objects.hash(getOperationType(), getAttributes(), getTransactionId(), getMediaType()); 233 } 234}