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.context.FhirContext; 023import ca.uhn.fhir.i18n.Msg; 024import ca.uhn.fhir.interceptor.model.RequestPartitionId; 025import ca.uhn.fhir.model.api.IModelJson; 026import ca.uhn.fhir.parser.IParser; 027import ca.uhn.fhir.rest.api.EncodingEnum; 028import ca.uhn.fhir.rest.api.server.RequestDetails; 029import ca.uhn.fhir.util.ResourceReferenceInfo; 030import com.fasterxml.jackson.annotation.JsonIgnore; 031import com.fasterxml.jackson.annotation.JsonProperty; 032import jakarta.annotation.Nonnull; 033import jakarta.annotation.Nullable; 034import org.apache.commons.lang3.StringUtils; 035import org.apache.commons.lang3.builder.ToStringBuilder; 036import org.hl7.fhir.instance.model.api.IBaseResource; 037import org.hl7.fhir.instance.model.api.IIdType; 038 039import java.util.List; 040 041import static org.apache.commons.lang3.StringUtils.isBlank; 042import static org.apache.commons.lang3.StringUtils.isNotBlank; 043 044public abstract class BaseResourceModifiedMessage extends BaseResourceMessage implements IResourceMessage, IModelJson { 045 046 @JsonProperty("payload") 047 protected String myPayload; 048 049 @JsonProperty("payloadId") 050 protected String myPayloadId; 051 052 @JsonProperty(value = "partitionId") 053 protected RequestPartitionId myPartitionId; 054 055 @JsonProperty(value = "payloadVersion") 056 protected String myPayloadVersion; 057 058 @JsonIgnore 059 protected transient IBaseResource myPayloadDecoded; 060 061 @JsonIgnore 062 protected transient String myPayloadType; 063 064 /** 065 * Constructor 066 */ 067 public BaseResourceModifiedMessage() { 068 super(); 069 } 070 071 public BaseResourceModifiedMessage(IIdType theIdType, OperationTypeEnum theOperationType) { 072 this(); 073 setOperationType(theOperationType); 074 setPayloadId(theIdType); 075 } 076 077 public BaseResourceModifiedMessage( 078 FhirContext theFhirContext, IBaseResource theResource, OperationTypeEnum theOperationType) { 079 this(); 080 setOperationType(theOperationType); 081 setNewPayload(theFhirContext, theResource); 082 } 083 084 public BaseResourceModifiedMessage( 085 FhirContext theFhirContext, 086 IBaseResource theNewResource, 087 OperationTypeEnum theOperationType, 088 RequestDetails theRequest) { 089 this(theFhirContext, theNewResource, theOperationType); 090 if (theRequest != null) { 091 setTransactionId(theRequest.getTransactionGuid()); 092 } 093 } 094 095 public BaseResourceModifiedMessage( 096 FhirContext theFhirContext, 097 IBaseResource theNewResource, 098 OperationTypeEnum theOperationType, 099 RequestDetails theRequest, 100 RequestPartitionId theRequestPartitionId) { 101 this(theFhirContext, theNewResource, theOperationType); 102 if (theRequest != null) { 103 setTransactionId(theRequest.getTransactionGuid()); 104 } 105 myPartitionId = theRequestPartitionId; 106 } 107 108 @Override 109 public String getPayloadId() { 110 return myPayloadId; 111 } 112 113 public String getPayloadVersion() { 114 return myPayloadVersion; 115 } 116 117 /** 118 * @since 5.6.0 119 */ 120 public void setPayloadId(IIdType thePayloadId) { 121 myPayloadId = null; 122 if (thePayloadId != null) { 123 myPayloadId = thePayloadId.toUnqualifiedVersionless().getValue(); 124 myPayloadVersion = thePayloadId.getVersionIdPart(); 125 } 126 } 127 128 /** 129 * @deprecated Use {@link #getPayloadId()} instead. Deprecated in 5.6.0 / 2021-10-27 130 */ 131 public String getId() { 132 return myPayloadId; 133 } 134 135 /** 136 * @deprecated Use {@link #setPayloadId(IIdType)}. Deprecated in 5.6.0 / 2021-10-27 137 */ 138 @Deprecated 139 public void setId(IIdType theId) { 140 setPayloadId(theId); 141 } 142 143 /** 144 * @deprecated Use {@link #getPayloadId(FhirContext)}. Deprecated in 5.6.0 / 2021-10-27 145 */ 146 public IIdType getId(FhirContext theCtx) { 147 return getPayloadId(theCtx); 148 } 149 150 /** 151 * @since 5.6.0 152 */ 153 public IIdType getPayloadId(FhirContext theCtx) { 154 IIdType retVal = null; 155 156 if (myPayloadId != null) { 157 retVal = theCtx.getVersion().newIdType().setValue(myPayloadId).withVersion(myPayloadVersion); 158 } 159 160 return retVal; 161 } 162 163 @Nullable 164 public IBaseResource getNewPayload(FhirContext theCtx) { 165 if (myPayloadDecoded == null && isNotBlank(myPayload)) { 166 myPayloadDecoded = theCtx.newJsonParser().parseResource(myPayload); 167 } 168 return myPayloadDecoded; 169 } 170 171 @Nullable 172 public IBaseResource getPayload(FhirContext theCtx) { 173 IBaseResource retVal = myPayloadDecoded; 174 if (retVal == null && isNotBlank(myPayload)) { 175 IParser parser = EncodingEnum.detectEncoding(myPayload).newParser(theCtx); 176 retVal = parser.parseResource(myPayload); 177 myPayloadDecoded = retVal; 178 } 179 return retVal; 180 } 181 182 @Nonnull 183 public String getPayloadString() { 184 if (this.myPayload != null) { 185 return this.myPayload; 186 } 187 188 return ""; 189 } 190 191 public void setNewPayload(FhirContext theCtx, IBaseResource thePayload) { 192 /* 193 * References with placeholders would be invalid by the time we get here, and 194 * would be caught before we even get here. This check is basically a last-ditch 195 * effort to make sure nothing has broken in the various safeguards that 196 * should prevent this from happening (hence it only being an assert as 197 * opposed to something executed all the time). 198 */ 199 assert payloadContainsNoPlaceholderReferences(theCtx, thePayload); 200 201 /* 202 * Note: Don't set myPayloadDecoded in here- This is a false optimization since 203 * it doesn't actually get used if anyone is doing subscriptions at any 204 * scale using a queue engine, and not going through the serialize/deserialize 205 * as we would in a queue engine can mask bugs. 206 * -JA 207 */ 208 myPayload = theCtx.newJsonParser().encodeResourceToString(thePayload); 209 210 setPayloadIdFromPayload(theCtx, thePayload); 211 } 212 213 private void setPayloadIdFromPayload(FhirContext theCtx, IBaseResource thePayload) { 214 IIdType payloadIdType = thePayload.getIdElement().toUnqualified(); 215 if (!payloadIdType.hasResourceType()) { 216 String resourceType = theCtx.getResourceType(thePayload); 217 payloadIdType = payloadIdType.withResourceType(resourceType); 218 } 219 220 setPayloadId(payloadIdType); 221 } 222 223 public RequestPartitionId getPartitionId() { 224 return myPartitionId; 225 } 226 227 public void setPartitionId(RequestPartitionId thePartitionId) { 228 myPartitionId = thePartitionId; 229 } 230 231 @Override 232 public String toString() { 233 return new ToStringBuilder(this) 234 .append("operationType", myOperationType) 235 .append("partitionId", myPartitionId) 236 .append("payloadId", myPayloadId) 237 .toString(); 238 } 239 240 protected static boolean payloadContainsNoPlaceholderReferences(FhirContext theCtx, IBaseResource theNewPayload) { 241 List<ResourceReferenceInfo> refs = theCtx.newTerser().getAllResourceReferences(theNewPayload); 242 for (ResourceReferenceInfo next : refs) { 243 String ref = next.getResourceReference().getReferenceElement().getValue(); 244 if (isBlank(ref)) { 245 IBaseResource resource = next.getResourceReference().getResource(); 246 if (resource != null) { 247 ref = resource.getIdElement().getValue(); 248 } 249 } 250 if (isNotBlank(ref)) { 251 if (ref.startsWith("#")) { 252 continue; 253 } 254 if (ref.startsWith("urn:uuid:")) { 255 throw new AssertionError(Msg.code(320) + "Reference at " + next.getName() + " is invalid: " + ref); 256 } 257 } 258 } 259 return true; 260 } 261 262 @Nullable 263 @Override 264 public String getMessageKeyOrDefault() { 265 return StringUtils.defaultString(super.getMessageKeyOrNull(), myPayloadId); 266 } 267 268 public boolean hasPayloadType(FhirContext theFhirContext, @Nonnull String theResourceName) { 269 if (myPayloadType == null) { 270 myPayloadType = getPayloadType(theFhirContext); 271 } 272 return theResourceName.equals(myPayloadType); 273 } 274 275 @Nullable 276 public String getPayloadType(FhirContext theFhirContext) { 277 String retval = null; 278 IIdType payloadId = getPayloadId(theFhirContext); 279 if (payloadId != null) { 280 retval = payloadId.getResourceType(); 281 } 282 if (isBlank(retval)) { 283 IBaseResource payload = getNewPayload(theFhirContext); 284 if (payload != null) { 285 retval = theFhirContext.getResourceType(payload); 286 } 287 } 288 return retval; 289 } 290}