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