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.i18n.Msg;
023import ca.uhn.fhir.model.api.IModelJson;
024import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
025import com.fasterxml.jackson.annotation.JsonProperty;
026import com.google.common.annotations.VisibleForTesting;
027import jakarta.annotation.Nullable;
028import org.apache.commons.lang3.ObjectUtils;
029import org.apache.commons.lang3.Validate;
030
031import java.util.Collections;
032import java.util.HashMap;
033import java.util.Map;
034import java.util.Optional;
035
036import static org.apache.commons.lang3.StringUtils.defaultString;
037
038@SuppressWarnings("WeakerAccess")
039public abstract class BaseResourceMessage implements IResourceMessage, IModelJson {
040
041        @JsonProperty("operationType")
042        protected BaseResourceModifiedMessage.OperationTypeEnum myOperationType;
043
044        @JsonProperty("attributes")
045        private Map<String, String> myAttributes;
046
047        @JsonProperty("transactionId")
048        private String myTransactionId;
049
050        @JsonProperty("mediaType")
051        private String myMediaType;
052
053        /**
054         * This is used by any message going to kafka for topic partition selection purposes.
055         */
056        @JsonProperty("messageKey")
057        private String myMessageKey;
058
059        /**
060         * Returns an attribute stored in this message.
061         * <p>
062         * Attributes are just a spot for user data of any kind to be
063         * added to the message for pasing along the subscription processing
064         * pipeline (typically by interceptors). Values will be carried from the beginning to the end.
065         * </p>
066         * <p>
067         * Note that messages are designed to be passed into queueing systems
068         * and serialized as JSON. As a result, only strings are currently allowed
069         * as values.
070         * </p>
071         */
072        public Optional<String> getAttribute(String theKey) {
073                Validate.notBlank(theKey);
074                if (myAttributes == null) {
075                        return Optional.empty();
076                }
077                return Optional.ofNullable(myAttributes.get(theKey));
078        }
079
080        /**
081         * Sets an attribute stored in this message.
082         * <p>
083         * Attributes are just a spot for user data of any kind to be
084         * added to the message for passing along the subscription processing
085         * pipeline (typically by interceptors). Values will be carried from the beginning to the end.
086         * </p>
087         * <p>
088         * Note that messages are designed to be passed into queueing systems
089         * and serialized as JSON. As a result, only strings are currently allowed
090         * as values.
091         * </p>
092         *
093         * @param theKey   The key (must not be null or blank)
094         * @param theValue The value (must not be null)
095         */
096        public void setAttribute(String theKey, String theValue) {
097                Validate.notBlank(theKey);
098                Validate.notNull(theValue);
099                if (myAttributes == null) {
100                        myAttributes = new HashMap<>();
101                }
102                myAttributes.put(theKey, theValue);
103        }
104
105        /**
106         * Copies any attributes from the given message into this messsage.
107         *
108         * @see #setAttribute(String, String)
109         * @see #getAttribute(String)
110         */
111        public void copyAdditionalPropertiesFrom(BaseResourceMessage theMsg) {
112                if (theMsg.myAttributes != null) {
113                        if (myAttributes == null) {
114                                myAttributes = new HashMap<>();
115                        }
116                        myAttributes.putAll(theMsg.myAttributes);
117                }
118        }
119
120        /**
121         * Returns the {@link OperationTypeEnum} that is occurring to the Resource of the message
122         *
123         * @return the operation type.
124         */
125        public BaseResourceModifiedMessage.OperationTypeEnum getOperationType() {
126                return myOperationType;
127        }
128
129        /**
130         * Sets the {@link OperationTypeEnum} occuring to the resource of the message.
131         *
132         * @param theOperationType The operation type to set.
133         */
134        public void setOperationType(BaseResourceModifiedMessage.OperationTypeEnum theOperationType) {
135                myOperationType = theOperationType;
136        }
137
138        /**
139         * Retrieve the transaction ID related to this message.
140         *
141         * @return the transaction ID, or null.
142         */
143        @Nullable
144        public String getTransactionId() {
145                return myTransactionId;
146        }
147
148        /**
149         * Adds a transaction ID to this message. This ID can be used for many purposes. For example, performing tracing
150         * across asynchronous hooks, tying data together, or downstream logging purposes.
151         * <p>
152         * One current internal implementation uses this field to tie back MDM processing results (which are asynchronous)
153         * to the original transaction log that caused the MDM processing to occur.
154         *
155         * @param theTransactionId An ID representing a transaction of relevance to this message.
156         */
157        public void setTransactionId(String theTransactionId) {
158                myTransactionId = theTransactionId;
159        }
160
161        public String getMediaType() {
162                return myMediaType;
163        }
164
165        public void setMediaType(String theMediaType) {
166                myMediaType = theMediaType;
167        }
168
169        @Deprecated
170        @Nullable
171        public String getMessageKeyOrNull() {
172                return getMessageKey();
173        }
174
175        @Nullable
176        public String getMessageKey() {
177                return myMessageKey;
178        }
179
180        public void setMessageKey(String theMessageKey) {
181                myMessageKey = theMessageKey;
182        }
183
184        /**
185         * Returns {@link #getMessageKey()} or {@link #getMessageKeyDefaultValue()} when {@link #getMessageKey()} returns <code>null</code>.
186         *
187         * @return the message key value or default
188         */
189        @Nullable
190        public String getMessageKeyOrDefault() {
191                return defaultString(getMessageKey(), getMessageKeyDefaultValue());
192        }
193
194        /**
195         * Provides a fallback value when method {@link #getMessageKey()} returns <code>null</code>.
196         *
197         * @return null by default
198         */
199        @Nullable
200        protected String getMessageKeyDefaultValue() {
201                return null;
202        }
203
204        public enum OperationTypeEnum {
205                CREATE(RestOperationTypeEnum.CREATE),
206                UPDATE(RestOperationTypeEnum.UPDATE),
207                DELETE(RestOperationTypeEnum.DELETE),
208                MANUALLY_TRIGGERED(RestOperationTypeEnum.UPDATE),
209                TRANSACTION(RestOperationTypeEnum.UPDATE);
210
211                private final RestOperationTypeEnum myRestOperationTypeEnum;
212
213                OperationTypeEnum(RestOperationTypeEnum theRestOperationTypeEnum) {
214                        myRestOperationTypeEnum = theRestOperationTypeEnum;
215                }
216
217                public static OperationTypeEnum from(RestOperationTypeEnum theRestOperationType) {
218                        switch (theRestOperationType) {
219                                case CREATE:
220                                        return CREATE;
221                                case UPDATE:
222                                        return UPDATE;
223                                case DELETE:
224                                        return DELETE;
225                                default:
226                                        throw new IllegalArgumentException(
227                                                        Msg.code(2348) + "Unsupported operation type: " + theRestOperationType);
228                        }
229                }
230
231                public RestOperationTypeEnum asRestOperationType() {
232                        return myRestOperationTypeEnum;
233                }
234        }
235
236        @VisibleForTesting
237        public Map<String, String> getAttributes() {
238                return ObjectUtils.defaultIfNull(myAttributes, Collections.emptyMap());
239        }
240}