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