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