001package ca.uhn.fhir.rest.server.messaging; 002 003/*- 004 * #%L 005 * HAPI FHIR - Server Framework 006 * %% 007 * Copyright (C) 2014 - 2022 Smile CDR, Inc. 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import ca.uhn.fhir.i18n.Msg; 024import ca.uhn.fhir.context.FhirContext; 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.builder.ToStringBuilder; 033import org.hl7.fhir.instance.model.api.IBaseResource; 034import org.hl7.fhir.instance.model.api.IIdType; 035 036import java.util.List; 037 038import static org.apache.commons.lang3.StringUtils.isBlank; 039import static org.apache.commons.lang3.StringUtils.isNotBlank; 040 041public abstract class BaseResourceModifiedMessage extends BaseResourceMessage implements IResourceMessage, IModelJson { 042 043 @JsonProperty("payload") 044 protected String myPayload; 045 @JsonProperty("payloadId") 046 protected String myPayloadId; 047 @JsonIgnore 048 protected transient IBaseResource myPayloadDecoded; 049 050 /** 051 * Constructor 052 */ 053 public BaseResourceModifiedMessage() { 054 super(); 055 } 056 057 public BaseResourceModifiedMessage(FhirContext theFhirContext, IBaseResource theResource, OperationTypeEnum theOperationType) { 058 this(); 059 setOperationType(theOperationType); 060 if (theOperationType != OperationTypeEnum.DELETE) { 061 setNewPayload(theFhirContext, theResource); 062 } else { 063 setPayloadIdFromPayload(theFhirContext, theResource); 064 } 065 } 066 067 public BaseResourceModifiedMessage(FhirContext theFhirContext, IBaseResource theNewResource, OperationTypeEnum theOperationType, RequestDetails theRequest) { 068 this(theFhirContext, theNewResource, theOperationType); 069 if (theRequest != null) { 070 setTransactionId(theRequest.getTransactionGuid()); 071 } 072 } 073 074 @Override 075 public String getPayloadId() { 076 return myPayloadId; 077 } 078 079 /** 080 * @since 5.6.0 081 */ 082 public void setPayloadId(IIdType thePayloadId) { 083 myPayloadId = null; 084 if (thePayloadId != null) { 085 myPayloadId = thePayloadId.getValue(); 086 } 087 } 088 089 /** 090 * @deprecated Use {@link #getPayloadId()} instead. Deprecated in 5.6.0 / 2021-10-27 091 */ 092 public String getId() { 093 return myPayloadId; 094 } 095 096 /** 097 * @deprecated Use {@link #setPayloadId(IIdType)}. Deprecated in 5.6.0 / 2021-10-27 098 */ 099 @Deprecated 100 public void setId(IIdType theId) { 101 setPayloadId(theId); 102 } 103 104 /** 105 * @deprecated Use {@link #getPayloadId(FhirContext)}. Deprecated in 5.6.0 / 2021-10-27 106 */ 107 public IIdType getId(FhirContext theCtx) { 108 return getPayloadId(theCtx); 109 } 110 111 /** 112 * @since 5.6.0 113 */ 114 public IIdType getPayloadId(FhirContext theCtx) { 115 IIdType retVal = null; 116 if (myPayloadId != null) { 117 retVal = theCtx.getVersion().newIdType().setValue(myPayloadId); 118 } 119 return retVal; 120 } 121 122 public IBaseResource getNewPayload(FhirContext theCtx) { 123 if (myPayloadDecoded == null && isNotBlank(myPayload)) { 124 myPayloadDecoded = theCtx.newJsonParser().parseResource(myPayload); 125 } 126 return myPayloadDecoded; 127 } 128 129 public IBaseResource getPayload(FhirContext theCtx) { 130 IBaseResource retVal = myPayloadDecoded; 131 if (retVal == null && isNotBlank(myPayload)) { 132 IParser parser = EncodingEnum.detectEncoding(myPayload).newParser(theCtx); 133 retVal = parser.parseResource(myPayload); 134 myPayloadDecoded = retVal; 135 } 136 return retVal; 137 } 138 139 public String getPayloadString() { 140 if (this.myPayload != null) { 141 return this.myPayload; 142 } 143 144 return ""; 145 } 146 147 protected void setNewPayload(FhirContext theCtx, IBaseResource thePayload) { 148 /* 149 * References with placeholders would be invalid by the time we get here, and 150 * would be caught before we even get here. This check is basically a last-ditch 151 * effort to make sure nothing has broken in the various safeguards that 152 * should prevent this from happening (hence it only being an assert as 153 * opposed to something executed all the time). 154 */ 155 assert payloadContainsNoPlaceholderReferences(theCtx, thePayload); 156 157 /* 158 * Note: Don't set myPayloadDecoded in here- This is a false optimization since 159 * it doesn't actually get used if anyone is doing subscriptions at any 160 * scale using a queue engine, and not going through the serialize/deserialize 161 * as we would in a queue engine can mask bugs. 162 * -JA 163 */ 164 myPayload = theCtx.newJsonParser().encodeResourceToString(thePayload); 165 166 setPayloadIdFromPayload(theCtx, thePayload); 167 } 168 169 private void setPayloadIdFromPayload(FhirContext theCtx, IBaseResource thePayload) { 170 IIdType payloadIdType = thePayload.getIdElement().toUnqualified(); 171 if (!payloadIdType.hasResourceType()) { 172 String resourceType = theCtx.getResourceType(thePayload); 173 payloadIdType = payloadIdType.withResourceType(resourceType); 174 } 175 176 setPayloadId(payloadIdType); 177 } 178 179 @Override 180 public String toString() { 181 return new ToStringBuilder(this) 182 .append("operationType", myOperationType) 183 .append("payloadId", myPayloadId) 184 .toString(); 185 } 186 187 protected static boolean payloadContainsNoPlaceholderReferences(FhirContext theCtx, IBaseResource theNewPayload) { 188 List<ResourceReferenceInfo> refs = theCtx.newTerser().getAllResourceReferences(theNewPayload); 189 for (ResourceReferenceInfo next : refs) { 190 String ref = next.getResourceReference().getReferenceElement().getValue(); 191 if (isBlank(ref)) { 192 IBaseResource resource = next.getResourceReference().getResource(); 193 if (resource != null) { 194 ref = resource.getIdElement().getValue(); 195 } 196 } 197 if (isNotBlank(ref)) { 198 if (ref.startsWith("#")) { 199 continue; 200 } 201 if (ref.startsWith("urn:uuid:")) { 202 throw new AssertionError(Msg.code(320) + "Reference at " + next.getName() + " is invalid: " + ref); 203 } 204 } 205 } 206 return true; 207 } 208} 209