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