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