001package ca.uhn.fhir.util;
002
003/*-
004 * #%L
005 * HAPI FHIR - Core Library
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
023
024import ca.uhn.fhir.context.FhirContext;
025import ca.uhn.fhir.i18n.Msg;
026import org.hl7.fhir.instance.model.api.IBase;
027import org.hl7.fhir.instance.model.api.IBaseDatatype;
028import org.hl7.fhir.instance.model.api.IBaseExtension;
029import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
030
031import java.util.List;
032import java.util.function.Predicate;
033import java.util.stream.Collectors;
034
035/**
036 * Utility for modifying with extensions in a FHIR version-independent approach.
037 */
038public class ExtensionUtil {
039
040        /**
041         * Non instantiable
042         */
043        private ExtensionUtil() {
044                // nothing
045        }
046
047        /**
048         * Returns an extension with the specified URL creating one if it doesn't exist.
049         *
050         * @param theBase Base resource to get extension from
051         * @param theUrl  URL for the extension
052         * @return Returns a extension with the specified URL.
053         * @throws IllegalArgumentException IllegalArgumentException is thrown in case resource doesn't support extensions
054         */
055        public static IBaseExtension<?, ?> getOrCreateExtension(IBase theBase, String theUrl) {
056                IBaseHasExtensions baseHasExtensions = validateExtensionSupport(theBase);
057                IBaseExtension<?,?> extension = getExtensionByUrl(baseHasExtensions, theUrl);
058                if (extension == null) {
059                        extension = baseHasExtensions.addExtension();
060                        extension.setUrl(theUrl);
061                }
062                return extension;
063        }
064
065        /**
066         * Returns an new empty extension.
067         *
068         * @param theBase Base resource to add the extension to
069         * @return Returns a new extension
070         * @throws IllegalArgumentException IllegalArgumentException is thrown in case resource doesn't support extensions
071         */
072        public static IBaseExtension<?, ?> addExtension(IBase theBase) {
073                return addExtension(theBase, null);
074        }
075
076        /**
077         * Returns an extension with the specified URL
078         *
079         * @param theBase Base resource to add the extension to
080         * @param theUrl  URL for the extension
081         * @return Returns a new extension with the specified URL.
082         * @throws IllegalArgumentException IllegalArgumentException is thrown in case resource doesn't support extensions
083         */
084        public static IBaseExtension<?, ?> addExtension(IBase theBase, String theUrl) {
085                IBaseHasExtensions baseHasExtensions = validateExtensionSupport(theBase);
086                IBaseExtension<?,?> extension = baseHasExtensions.addExtension();
087                if (theUrl != null) {
088                        extension.setUrl(theUrl);
089                }
090                return extension;
091        }
092
093        /**
094         * Adds an extension with the specified value
095         *
096         * @param theBase        The resource to update extension on
097         * @param theUrl         Extension URL
098         * @param theValueType   Type of the value to set in the extension
099         * @param theValue       Extension value
100         * @param theFhirContext The context containing FHIR resource definitions
101         */
102        public static void addExtension(FhirContext theFhirContext, IBase theBase, String theUrl, String theValueType, Object theValue) {
103                IBaseExtension<?,?> ext = addExtension(theBase, theUrl);
104                setExtension(theFhirContext, ext, theValueType, theValue);
105        }
106
107        private static IBaseHasExtensions validateExtensionSupport(IBase theBase) {
108                if (!(theBase instanceof IBaseHasExtensions)) {
109                        throw new IllegalArgumentException(Msg.code(1747) + String.format("Expected instance that supports extensions, but got %s", theBase));
110                }
111                return (IBaseHasExtensions) theBase;
112        }
113
114        /**
115         * Checks if the specified instance has an extension with the specified URL
116         *
117         * @param theBase         The base resource to check extensions on
118         * @param theExtensionUrl URL of the extension
119         * @return Returns true if extension is exists and false otherwise
120         */
121        public static boolean hasExtension(IBase theBase, String theExtensionUrl) {
122                IBaseHasExtensions baseHasExtensions;
123                try {
124                        baseHasExtensions = validateExtensionSupport(theBase);
125                } catch (Exception e) {
126                        return false;
127                }
128
129                return getExtensionByUrl(baseHasExtensions, theExtensionUrl) != null;
130        }
131
132        /**
133         * Checks if the specified instance has an extension with the specified URL
134         *
135         * @param theBase         The base resource to check extensions on
136         * @param theExtensionUrl URL of the extension
137         * @return Returns true if extension is exists and false otherwise
138         */
139        public static boolean hasExtension(IBase theBase, String theExtensionUrl, String theExtensionValue) {
140                if (!hasExtension(theBase, theExtensionUrl)) {
141                        return false;
142                }
143                IBaseDatatype value = getExtensionByUrl(theBase, theExtensionUrl).getValue();
144                if (value == null) {
145                        return theExtensionValue == null;
146                }
147                return value.toString().equals(theExtensionValue);
148        }
149
150        /**
151         * Gets the first extension with the specified URL
152         *
153         * @param theBase         The resource to get the extension for
154         * @param theExtensionUrl URL of the extension to get. Must be non-null
155         * @return Returns the first available extension with the specified URL, or null if such extension doesn't exist
156         */
157        public static IBaseExtension<?, ?> getExtensionByUrl(IBase theBase, String theExtensionUrl) {
158                Predicate<IBaseExtension<?,?>> filter;
159                if (theExtensionUrl == null) {
160                        filter = (e -> true);
161                } else {
162                        filter = (e -> theExtensionUrl.equals(e.getUrl()));
163                }
164
165                return getExtensionsMatchingPredicate(theBase, filter)
166                        .stream()
167                        .findFirst()
168                        .orElse(null);
169        }
170
171        /**
172         * Gets all extensions that match the specified filter predicate
173         *
174         * @param theBase   The resource to get the extension for
175         * @param theFilter Predicate to match the extension against
176         * @return Returns all extension with the specified URL, or an empty list if such extensions do not exist
177         */
178        public static List<IBaseExtension<?, ?>> getExtensionsMatchingPredicate(IBase theBase, Predicate<? super IBaseExtension<?,?>> theFilter) {
179                return validateExtensionSupport(theBase)
180                        .getExtension()
181                        .stream()
182                        .filter(theFilter)
183                        .collect(Collectors.toList());
184        }
185
186        /**
187         * Removes all extensions.
188         *
189         * @param theBase The resource to clear the extension for
190         * @return Returns all extension that were removed
191         */
192        public static List<IBaseExtension<?, ?>> clearAllExtensions(IBase theBase) {
193                return clearExtensionsMatchingPredicate(theBase, (e -> true));
194        }
195
196        /**
197         * Removes all extensions by URL.
198         *
199         * @param theBase The resource to clear the extension for
200         * @param theUrl  The url to clear extensions for
201         * @return Returns all extension that were removed
202         */
203        public static List<IBaseExtension<?, ?>> clearExtensionsByUrl(IBase theBase, String theUrl) {
204                return clearExtensionsMatchingPredicate(theBase, (e -> theUrl.equals(e.getUrl())));
205        }
206
207        /**
208         * Removes all extensions that match the specified predicate
209         *
210         * @param theBase   The base object to clear the extension for
211         * @param theFilter Defines which extensions should be cleared
212         * @return Returns all extension that were removed
213         */
214        private static List<IBaseExtension<?, ?>> clearExtensionsMatchingPredicate(IBase theBase, Predicate<? super IBaseExtension<?,?>> theFilter) {
215                List<IBaseExtension<?, ?>> retVal = getExtensionsMatchingPredicate(theBase, theFilter);
216                validateExtensionSupport(theBase)
217                        .getExtension()
218                        .removeIf(theFilter);
219                return retVal;
220        }
221
222        /**
223         * Gets all extensions with the specified URL
224         *
225         * @param theBase         The resource to get the extension for
226         * @param theExtensionUrl URL of the extension to get. Must be non-null
227         * @return Returns all extension with the specified URL, or an empty list if such extensions do not exist
228         */
229        public static List<IBaseExtension<?, ?>> getExtensionsByUrl(IBaseHasExtensions theBase, String theExtensionUrl) {
230                Predicate<IBaseExtension<?,?>> urlEqualityPredicate = e -> theExtensionUrl.equals(e.getUrl());
231                return getExtensionsMatchingPredicate(theBase, urlEqualityPredicate);
232        }
233
234        /**
235         * Sets value of the extension as a string
236         *
237         * @param theExtension   The extension to set the value on
238         * @param theValue       The value to set
239         * @param theFhirContext The context containing FHIR resource definitions
240         */
241        public static void setExtension(FhirContext theFhirContext, IBaseExtension<?,?> theExtension, String theValue) {
242                setExtension(theFhirContext, theExtension, "string", theValue);
243        }
244
245        /**
246         * Sets value of the extension
247         *
248         * @param theExtension     The extension to set the value on
249         * @param theExtensionType Element type of the extension
250         * @param theValue         The value to set
251         * @param theFhirContext   The context containing FHIR resource definitions
252         */
253        public static void setExtension(FhirContext theFhirContext, IBaseExtension<?,?> theExtension, String theExtensionType, Object theValue) {
254                theExtension.setValue(TerserUtil.newElement(theFhirContext, theExtensionType, theValue));
255        }
256
257        /**
258         * Sets or replaces existing extension with the specified value as a string
259         *
260         * @param theBase        The resource to update extension on
261         * @param theUrl         Extension URL
262         * @param theValue       Extension value
263         * @param theFhirContext The context containing FHIR resource definitions
264         */
265        public static void setExtensionAsString(FhirContext theFhirContext, IBase theBase, String theUrl, String theValue) {
266                IBaseExtension<?,?> ext = getOrCreateExtension(theBase, theUrl);
267                setExtension(theFhirContext, ext, theValue);
268        }
269
270        /**
271         * Sets or replaces existing extension with the specified value
272         *
273         * @param theBase        The resource to update extension on
274         * @param theUrl         Extension URL
275         * @param theValueType   Type of the value to set in the extension
276         * @param theValue       Extension value
277         * @param theFhirContext The context containing FHIR resource definitions
278         */
279        public static void setExtension(FhirContext theFhirContext, IBase theBase, String theUrl, String theValueType, Object theValue) {
280                IBaseExtension<?,?> ext = getOrCreateExtension(theBase, theUrl);
281                setExtension(theFhirContext, ext, theValueType, theValue);
282        }
283
284        /**
285         * Compares two extensions, returns true if they have the same value and url
286         *
287         * @param theLeftExtension  : Extension to be evaluated #1
288         * @param theRightExtension : Extension to be evaluated #2
289         * @return Result of the comparison
290         */
291        public static boolean equals(IBaseExtension<?,?> theLeftExtension, IBaseExtension<?,?> theRightExtension) {
292                return TerserUtil.equals(theLeftExtension, theRightExtension);
293        }
294}