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