
001/* 002 * #%L 003 * HAPI FHIR - Core Library 004 * %% 005 * Copyright (C) 2014 - 2025 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 022import ca.uhn.fhir.context.BaseRuntimeChildDefinition; 023import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; 024import ca.uhn.fhir.context.BaseRuntimeElementDefinition; 025import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum; 026import ca.uhn.fhir.context.ConfigurationException; 027import ca.uhn.fhir.context.FhirContext; 028import ca.uhn.fhir.context.FhirVersionEnum; 029import ca.uhn.fhir.context.RuntimeChildChoiceDefinition; 030import ca.uhn.fhir.context.RuntimeChildDirectResource; 031import ca.uhn.fhir.context.RuntimeExtensionDtDefinition; 032import ca.uhn.fhir.context.RuntimeResourceDefinition; 033import ca.uhn.fhir.context.RuntimeSearchParam; 034import ca.uhn.fhir.i18n.Msg; 035import ca.uhn.fhir.model.api.ExtensionDt; 036import ca.uhn.fhir.model.api.IIdentifiableElement; 037import ca.uhn.fhir.model.api.IResource; 038import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions; 039import ca.uhn.fhir.model.base.composite.BaseContainedDt; 040import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt; 041import ca.uhn.fhir.model.primitive.StringDt; 042import ca.uhn.fhir.parser.DataFormatException; 043import com.google.common.collect.Lists; 044import jakarta.annotation.Nonnull; 045import jakarta.annotation.Nullable; 046import org.apache.commons.lang3.StringUtils; 047import org.apache.commons.lang3.Validate; 048import org.hl7.fhir.instance.model.api.IBase; 049import org.hl7.fhir.instance.model.api.IBaseElement; 050import org.hl7.fhir.instance.model.api.IBaseExtension; 051import org.hl7.fhir.instance.model.api.IBaseHasExtensions; 052import org.hl7.fhir.instance.model.api.IBaseHasModifierExtensions; 053import org.hl7.fhir.instance.model.api.IBaseReference; 054import org.hl7.fhir.instance.model.api.IBaseResource; 055import org.hl7.fhir.instance.model.api.IDomainResource; 056import org.hl7.fhir.instance.model.api.IIdType; 057import org.hl7.fhir.instance.model.api.IPrimitiveType; 058import org.slf4j.Logger; 059import org.slf4j.LoggerFactory; 060 061import java.util.ArrayList; 062import java.util.Arrays; 063import java.util.Collection; 064import java.util.Collections; 065import java.util.Comparator; 066import java.util.HashMap; 067import java.util.HashSet; 068import java.util.IdentityHashMap; 069import java.util.Iterator; 070import java.util.List; 071import java.util.Map; 072import java.util.Objects; 073import java.util.Optional; 074import java.util.Set; 075import java.util.UUID; 076import java.util.regex.Matcher; 077import java.util.regex.Pattern; 078import java.util.stream.Collectors; 079import java.util.stream.Stream; 080 081import static org.apache.commons.lang3.StringUtils.defaultString; 082import static org.apache.commons.lang3.StringUtils.isBlank; 083import static org.apache.commons.lang3.StringUtils.isEmpty; 084import static org.apache.commons.lang3.StringUtils.isNotBlank; 085import static org.apache.commons.lang3.function.Consumers.nop; 086 087public class FhirTerser { 088 089 private static final Pattern COMPARTMENT_MATCHER_PATH = 090 Pattern.compile("([a-zA-Z.]+)\\.where\\(resolve\\(\\) is ([a-zA-Z]+)\\)"); 091 092 private static final String USER_DATA_KEY_CONTAIN_RESOURCES_COMPLETED = 093 FhirTerser.class.getName() + "_CONTAIN_RESOURCES_COMPLETED"; 094 095 private final FhirContext myContext; 096 private static final Logger ourLog = LoggerFactory.getLogger(FhirTerser.class); 097 098 /** 099 * This comparator sorts IBaseReferences, and places any that are missing an ID at the end. Those with an ID go to the front. 100 */ 101 private static final Comparator<IBaseReference> REFERENCES_WITH_IDS_FIRST = 102 Comparator.nullsLast(Comparator.comparing(ref -> { 103 if (ref.getResource() == null) return true; 104 if (ref.getResource().getIdElement() == null) return true; 105 if (ref.getResource().getIdElement().getValue() == null) return true; 106 return false; 107 })); 108 109 public FhirTerser(FhirContext theContext) { 110 super(); 111 myContext = theContext; 112 } 113 114 private List<String> addNameToList(List<String> theCurrentList, BaseRuntimeChildDefinition theChildDefinition) { 115 if (theChildDefinition == null) return null; 116 if (theCurrentList == null || theCurrentList.isEmpty()) 117 return new ArrayList<>(Collections.singletonList(theChildDefinition.getElementName())); 118 List<String> newList = new ArrayList<>(theCurrentList); 119 newList.add(theChildDefinition.getElementName()); 120 return newList; 121 } 122 123 private ExtensionDt createEmptyExtensionDt(IBaseExtension<?, ?> theBaseExtension, String theUrl) { 124 return createEmptyExtensionDt(theBaseExtension, false, theUrl); 125 } 126 127 @SuppressWarnings("unchecked") 128 private ExtensionDt createEmptyExtensionDt(IBaseExtension theBaseExtension, boolean theIsModifier, String theUrl) { 129 ExtensionDt retVal = new ExtensionDt(theIsModifier, theUrl); 130 theBaseExtension.getExtension().add(retVal); 131 return retVal; 132 } 133 134 private ExtensionDt createEmptyExtensionDt( 135 ISupportsUndeclaredExtensions theSupportsUndeclaredExtensions, String theUrl) { 136 return createEmptyExtensionDt(theSupportsUndeclaredExtensions, false, theUrl); 137 } 138 139 private ExtensionDt createEmptyExtensionDt( 140 ISupportsUndeclaredExtensions theSupportsUndeclaredExtensions, boolean theIsModifier, String theUrl) { 141 return theSupportsUndeclaredExtensions.addUndeclaredExtension(theIsModifier, theUrl); 142 } 143 144 private IBaseExtension<?, ?> createEmptyExtension(IBaseHasExtensions theBaseHasExtensions, String theUrl) { 145 return (IBaseExtension<?, ?>) theBaseHasExtensions.addExtension().setUrl(theUrl); 146 } 147 148 private IBaseExtension<?, ?> createEmptyModifierExtension( 149 IBaseHasModifierExtensions theBaseHasModifierExtensions, String theUrl) { 150 return (IBaseExtension<?, ?>) 151 theBaseHasModifierExtensions.addModifierExtension().setUrl(theUrl); 152 } 153 154 private ExtensionDt createEmptyModifierExtensionDt( 155 ISupportsUndeclaredExtensions theSupportsUndeclaredExtensions, String theUrl) { 156 return createEmptyExtensionDt(theSupportsUndeclaredExtensions, true, theUrl); 157 } 158 159 /** 160 * Clones all values from a source object into the equivalent fields in a target object 161 * 162 * @param theSource The source object (must not be null) 163 * @param theTarget The target object to copy values into (must not be null) 164 * @param theIgnoreMissingFields The ignore fields in the target which do not exist (if false, an exception will be thrown if the target is unable to accept a value from the source) 165 * @return Returns the target (which will be the same object that was passed into theTarget) for easy chaining 166 */ 167 public IBase cloneInto(IBase theSource, IBase theTarget, boolean theIgnoreMissingFields) { 168 Validate.notNull(theSource, "theSource must not be null"); 169 Validate.notNull(theTarget, "theTarget must not be null"); 170 171 // DSTU3+ 172 if (theSource instanceof IBaseElement) { 173 IBaseElement source = (IBaseElement) theSource; 174 IBaseElement target = (IBaseElement) theTarget; 175 target.setId(source.getId()); 176 } 177 178 // DSTU2 only 179 if (theSource instanceof IIdentifiableElement) { 180 IIdentifiableElement source = (IIdentifiableElement) theSource; 181 IIdentifiableElement target = (IIdentifiableElement) theTarget; 182 target.setElementSpecificId(source.getElementSpecificId()); 183 } 184 185 // DSTU2 only 186 if (theSource instanceof IResource) { 187 IResource source = (IResource) theSource; 188 IResource target = (IResource) theTarget; 189 target.setId(source.getId()); 190 target.getResourceMetadata().putAll(source.getResourceMetadata()); 191 } 192 193 if (theSource instanceof IPrimitiveType<?>) { 194 if (theTarget instanceof IPrimitiveType<?>) { 195 String valueAsString = ((IPrimitiveType<?>) theSource).getValueAsString(); 196 if (isNotBlank(valueAsString)) { 197 ((IPrimitiveType<?>) theTarget).setValueAsString(valueAsString); 198 } 199 if (theSource instanceof IBaseHasExtensions && theTarget instanceof IBaseHasExtensions) { 200 List<? extends IBaseExtension<?, ?>> extensions = ((IBaseHasExtensions) theSource).getExtension(); 201 for (IBaseExtension<?, ?> nextSource : extensions) { 202 IBaseExtension<?, ?> nextTarget = ((IBaseHasExtensions) theTarget).addExtension(); 203 cloneInto(nextSource, nextTarget, theIgnoreMissingFields); 204 } 205 } 206 return theSource; 207 } 208 if (theIgnoreMissingFields) { 209 return theSource; 210 } 211 throw new DataFormatException(Msg.code(1788) + "Can not copy value from primitive of type " 212 + theSource.getClass().getName() + " into type " 213 + theTarget.getClass().getName()); 214 } 215 216 if (theSource instanceof IBaseReference && theTarget instanceof IBaseReference) { 217 IBaseReference sourceReference = (IBaseReference) theSource; 218 IBaseReference targetReference = (IBaseReference) theTarget; 219 if (sourceReference.getResource() != null) { 220 targetReference.setResource(sourceReference.getResource()); 221 } 222 } 223 224 BaseRuntimeElementCompositeDefinition<?> sourceDef = 225 (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theSource.getClass()); 226 BaseRuntimeElementCompositeDefinition<?> targetDef = 227 (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theTarget.getClass()); 228 229 List<BaseRuntimeChildDefinition> children = sourceDef.getChildren(); 230 if (sourceDef instanceof RuntimeExtensionDtDefinition) { 231 children = ((RuntimeExtensionDtDefinition) sourceDef).getChildrenIncludingUrl(); 232 } 233 234 for (BaseRuntimeChildDefinition nextChild : children) 235 for (IBase nextValue : nextChild.getAccessor().getValues(theSource)) { 236 Class<? extends IBase> valueType = nextValue.getClass(); 237 String elementName = nextChild.getChildNameByDatatype(valueType); 238 BaseRuntimeChildDefinition targetChild = targetDef.getChildByName(elementName); 239 if (targetChild == null) { 240 if (theIgnoreMissingFields) { 241 continue; 242 } 243 throw new DataFormatException(Msg.code(1789) + "Type " 244 + theTarget.getClass().getName() + " does not have a child with name " + elementName); 245 } 246 247 BaseRuntimeElementDefinition<?> element = myContext.getElementDefinition(valueType); 248 Object instanceConstructorArg = targetChild.getInstanceConstructorArguments(); 249 IBase target; 250 if (element == null && BaseContainedDt.class.isAssignableFrom(valueType)) { 251 /* 252 * This is a hack for DSTU2 - The way we did contained resources in 253 * the DSTU2 model was weird, since the element isn't actually a FHIR type. 254 * This is fixed in DSTU3+ so this hack only applies there. 255 */ 256 BaseContainedDt containedTarget = (BaseContainedDt) ReflectionUtil.newInstance(valueType); 257 BaseContainedDt containedSource = (BaseContainedDt) nextValue; 258 for (IResource next : containedSource.getContainedResources()) { 259 List containedResources = containedTarget.getContainedResources(); 260 containedResources.add(next); 261 } 262 targetChild.getMutator().addValue(theTarget, containedTarget); 263 continue; 264 } else if (instanceConstructorArg != null) { 265 target = element.newInstance(instanceConstructorArg); 266 } else { 267 target = element.newInstance(); 268 } 269 270 targetChild.getMutator().addValue(theTarget, target); 271 cloneInto(nextValue, target, theIgnoreMissingFields); 272 } 273 274 return theTarget; 275 } 276 277 /** 278 * Returns a list containing all child elements (including the resource itself) which are <b>non-empty</b> and are either of the exact type specified, or are a subclass of that type. 279 * <p> 280 * For example, specifying a type of {@link StringDt} would return all non-empty string instances within the message. Specifying a type of {@link IResource} would return the resource itself, as 281 * well as any contained resources. 282 * </p> 283 * <p> 284 * Note on scope: This method will descend into any contained resources ({@link IResource#getContained()}) as well, but will not descend into linked resources (e.g. 285 * {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource) 286 * </p> 287 * 288 * @param theResource The resource instance to search. Must not be null. 289 * @param theType The type to search for. Must not be null. 290 * @return Returns a list of all matching elements 291 */ 292 public <T extends IBase> List<T> getAllPopulatedChildElementsOfType( 293 IBaseResource theResource, final Class<T> theType) { 294 final ArrayList<T> retVal = new ArrayList<>(); 295 BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource); 296 visit(newMap(), theResource, theResource, null, null, def, new IModelVisitor() { 297 @SuppressWarnings("unchecked") 298 @Override 299 public void acceptElement( 300 IBaseResource theOuterResource, 301 IBase theElement, 302 List<String> thePathToElement, 303 BaseRuntimeChildDefinition theChildDefinition, 304 BaseRuntimeElementDefinition<?> theDefinition) { 305 if (theElement == null || theElement.isEmpty()) { 306 return; 307 } 308 309 if (theType.isAssignableFrom(theElement.getClass())) { 310 retVal.add((T) theElement); 311 } 312 } 313 }); 314 return retVal; 315 } 316 317 /** 318 * Extracts all outbound references from a resource 319 * 320 * @param theResource the resource to be analyzed 321 * @return a list of references to other resources 322 */ 323 public List<ResourceReferenceInfo> getAllResourceReferences(final IBaseResource theResource) { 324 return getAllResourceReferencesExcluding(theResource, Lists.newArrayList()); 325 } 326 327 /** 328 * Extracts all outbound references from a resource, excluding any that are located on black-listed parts of the 329 * resource 330 * 331 * @param theResource the resource to be analyzed 332 * @param thePathsToExclude a list of dot-delimited paths not to include in the result 333 * @return a list of references to other resources 334 */ 335 public List<ResourceReferenceInfo> getAllResourceReferencesExcluding( 336 final IBaseResource theResource, List<String> thePathsToExclude) { 337 final ArrayList<ResourceReferenceInfo> retVal = new ArrayList<>(); 338 BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource); 339 List<List<String>> tokenizedPathsToExclude = thePathsToExclude.stream() 340 .map(path -> StringUtils.split(path, ".")) 341 .map(Lists::newArrayList) 342 .collect(Collectors.toList()); 343 344 visit(newMap(), theResource, theResource, null, null, def, new IModelVisitor() { 345 @Override 346 public void acceptElement( 347 IBaseResource theOuterResource, 348 IBase theElement, 349 List<String> thePathToElement, 350 BaseRuntimeChildDefinition theChildDefinition, 351 BaseRuntimeElementDefinition<?> theDefinition) { 352 if (theElement == null || theElement.isEmpty()) { 353 return; 354 } 355 356 if (thePathToElement != null && pathShouldBeExcluded(tokenizedPathsToExclude, thePathToElement)) { 357 return; 358 } 359 if (IBaseReference.class.isAssignableFrom(theElement.getClass())) { 360 retVal.add(new ResourceReferenceInfo( 361 myContext, theOuterResource, thePathToElement, (IBaseReference) theElement)); 362 } 363 } 364 }); 365 return retVal; 366 } 367 368 private boolean pathShouldBeExcluded(List<List<String>> theTokenizedPathsToExclude, List<String> thePathToElement) { 369 return theTokenizedPathsToExclude.stream().anyMatch(p -> { 370 // Check whether the path to the element starts with the path to be excluded 371 if (p.size() > thePathToElement.size()) { 372 return false; 373 } 374 375 List<String> prefix = thePathToElement.subList(0, p.size()); 376 377 return Objects.equals(p, prefix); 378 }); 379 } 380 381 private BaseRuntimeChildDefinition getDefinition( 382 BaseRuntimeElementCompositeDefinition<?> theCurrentDef, List<String> theSubList) { 383 BaseRuntimeChildDefinition nextDef = theCurrentDef.getChildByNameOrThrowDataFormatException(theSubList.get(0)); 384 385 if (theSubList.size() == 1) { 386 return nextDef; 387 } 388 BaseRuntimeElementCompositeDefinition<?> cmp = 389 (BaseRuntimeElementCompositeDefinition<?>) nextDef.getChildByName(theSubList.get(0)); 390 return getDefinition(cmp, theSubList.subList(1, theSubList.size())); 391 } 392 393 public BaseRuntimeChildDefinition getDefinition(Class<? extends IBaseResource> theResourceType, String thePath) { 394 RuntimeResourceDefinition def = myContext.getResourceDefinition(theResourceType); 395 396 List<String> parts = Arrays.asList(thePath.split("\\.")); 397 List<String> subList = parts.subList(1, parts.size()); 398 if (subList.size() < 1) { 399 throw new ConfigurationException(Msg.code(1790) + "Invalid path: " + thePath); 400 } 401 return getDefinition(def, subList); 402 } 403 404 public Object getSingleValueOrNull(IBase theTarget, String thePath) { 405 Class<IBase> wantedType = IBase.class; 406 407 return getSingleValueOrNull(theTarget, thePath, wantedType); 408 } 409 410 public <T extends IBase> T getSingleValueOrNull(IBase theTarget, String thePath, Class<T> theWantedType) { 411 Validate.notNull(theTarget, "theTarget must not be null"); 412 Validate.notBlank(thePath, "thePath must not be empty"); 413 414 BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(theTarget.getClass()); 415 if (!(def instanceof BaseRuntimeElementCompositeDefinition)) { 416 throw new IllegalArgumentException(Msg.code(1791) + "Target is not a composite type: " 417 + theTarget.getClass().getName()); 418 } 419 420 BaseRuntimeElementCompositeDefinition<?> currentDef = (BaseRuntimeElementCompositeDefinition<?>) def; 421 422 List<String> parts = parsePath(currentDef, thePath); 423 424 List<T> retVal = getValues(currentDef, theTarget, parts, theWantedType); 425 if (retVal.isEmpty()) { 426 return null; 427 } 428 return retVal.get(0); 429 } 430 431 public Optional<String> getSinglePrimitiveValue(IBase theTarget, String thePath) { 432 return getSingleValue(theTarget, thePath, IPrimitiveType.class).map(t -> t.getValueAsString()); 433 } 434 435 public String getSinglePrimitiveValueOrNull(IBase theTarget, String thePath) { 436 return getSingleValue(theTarget, thePath, IPrimitiveType.class) 437 .map(IPrimitiveType::getValueAsString) 438 .orElse(null); 439 } 440 441 public <T extends IBase> Optional<T> getSingleValue(IBase theTarget, String thePath, Class<T> theWantedType) { 442 return Optional.ofNullable(getSingleValueOrNull(theTarget, thePath, theWantedType)); 443 } 444 445 private <T extends IBase> List<T> getValues( 446 BaseRuntimeElementCompositeDefinition<?> theCurrentDef, 447 IBase theCurrentObj, 448 List<String> theSubList, 449 Class<T> theWantedClass) { 450 return getValues(theCurrentDef, theCurrentObj, theSubList, theWantedClass, false, false); 451 } 452 453 @SuppressWarnings("unchecked") 454 private <T extends IBase> List<T> getValues( 455 BaseRuntimeElementCompositeDefinition<?> theCurrentDef, 456 IBase theCurrentObj, 457 List<String> theSubList, 458 Class<T> theWantedClass, 459 boolean theCreate, 460 boolean theAddExtension) { 461 if (theSubList.isEmpty()) { 462 return Collections.emptyList(); 463 } 464 465 String name = theSubList.get(0); 466 List<T> retVal = new ArrayList<>(); 467 468 if (name.startsWith("extension('")) { 469 String extensionUrl = name.substring("extension('".length()); 470 int endIndex = extensionUrl.indexOf('\''); 471 if (endIndex != -1) { 472 extensionUrl = extensionUrl.substring(0, endIndex); 473 } 474 475 if (myContext.getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3)) { 476 // DTSU2 477 final String extensionDtUrlForLambda = extensionUrl; 478 List<ExtensionDt> extensionDts = Collections.emptyList(); 479 if (theCurrentObj instanceof ISupportsUndeclaredExtensions) { 480 extensionDts = ((ISupportsUndeclaredExtensions) theCurrentObj) 481 .getUndeclaredExtensions().stream() 482 .filter(t -> t.getUrl().equals(extensionDtUrlForLambda)) 483 .collect(Collectors.toList()); 484 485 if (theAddExtension 486 && (!(theCurrentObj instanceof IBaseExtension) 487 || (extensionDts.isEmpty() && theSubList.size() == 1))) { 488 extensionDts.add( 489 createEmptyExtensionDt((ISupportsUndeclaredExtensions) theCurrentObj, extensionUrl)); 490 } 491 492 if (extensionDts.isEmpty() && theCreate) { 493 extensionDts.add( 494 createEmptyExtensionDt((ISupportsUndeclaredExtensions) theCurrentObj, extensionUrl)); 495 } 496 497 } else if (theCurrentObj instanceof IBaseExtension) { 498 extensionDts = ((IBaseExtension) theCurrentObj).getExtension(); 499 500 if (theAddExtension && (extensionDts.isEmpty() && theSubList.size() == 1)) { 501 extensionDts.add(createEmptyExtensionDt((IBaseExtension) theCurrentObj, extensionUrl)); 502 } 503 504 if (extensionDts.isEmpty() && theCreate) { 505 extensionDts.add(createEmptyExtensionDt((IBaseExtension) theCurrentObj, extensionUrl)); 506 } 507 } 508 509 for (ExtensionDt next : extensionDts) { 510 if (theWantedClass.isAssignableFrom(next.getClass())) { 511 retVal.add((T) next); 512 } 513 } 514 } else { 515 // DSTU3+ 516 final String extensionUrlForLambda = extensionUrl; 517 List<IBaseExtension<?, ?>> extensions = Collections.emptyList(); 518 if (theCurrentObj instanceof IBaseHasExtensions) { 519 extensions = ((IBaseHasExtensions) theCurrentObj) 520 .getExtension().stream() 521 .filter(t -> t.getUrl().equals(extensionUrlForLambda)) 522 .collect(Collectors.toList()); 523 524 if (theAddExtension 525 && (!(theCurrentObj instanceof IBaseExtension) 526 || (extensions.isEmpty() && theSubList.size() == 1))) { 527 extensions.add(createEmptyExtension((IBaseHasExtensions) theCurrentObj, extensionUrl)); 528 } 529 530 if (extensions.isEmpty() && theCreate) { 531 extensions.add(createEmptyExtension((IBaseHasExtensions) theCurrentObj, extensionUrl)); 532 } 533 } 534 535 for (IBaseExtension<?, ?> next : extensions) { 536 if (theWantedClass.isAssignableFrom(next.getClass())) { 537 retVal.add((T) next); 538 } 539 } 540 } 541 542 if (theSubList.size() > 1) { 543 List<T> values = retVal; 544 retVal = new ArrayList<>(); 545 for (T nextElement : values) { 546 BaseRuntimeElementCompositeDefinition<?> nextChildDef = (BaseRuntimeElementCompositeDefinition<?>) 547 myContext.getElementDefinition(nextElement.getClass()); 548 List<T> foundValues = getValues( 549 nextChildDef, 550 nextElement, 551 theSubList.subList(1, theSubList.size()), 552 theWantedClass, 553 theCreate, 554 theAddExtension); 555 retVal.addAll(foundValues); 556 } 557 } 558 559 return retVal; 560 } 561 562 if (name.startsWith("modifierExtension('")) { 563 String extensionUrl = name.substring("modifierExtension('".length()); 564 int endIndex = extensionUrl.indexOf('\''); 565 if (endIndex != -1) { 566 extensionUrl = extensionUrl.substring(0, endIndex); 567 } 568 569 if (myContext.getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3)) { 570 // DSTU2 571 final String extensionDtUrlForLambda = extensionUrl; 572 List<ExtensionDt> extensionDts = Collections.emptyList(); 573 if (theCurrentObj instanceof ISupportsUndeclaredExtensions) { 574 extensionDts = ((ISupportsUndeclaredExtensions) theCurrentObj) 575 .getUndeclaredModifierExtensions().stream() 576 .filter(t -> t.getUrl().equals(extensionDtUrlForLambda)) 577 .collect(Collectors.toList()); 578 579 if (theAddExtension 580 && (!(theCurrentObj instanceof IBaseExtension) 581 || (extensionDts.isEmpty() && theSubList.size() == 1))) { 582 extensionDts.add(createEmptyModifierExtensionDt( 583 (ISupportsUndeclaredExtensions) theCurrentObj, extensionUrl)); 584 } 585 586 if (extensionDts.isEmpty() && theCreate) { 587 extensionDts.add(createEmptyModifierExtensionDt( 588 (ISupportsUndeclaredExtensions) theCurrentObj, extensionUrl)); 589 } 590 591 } else if (theCurrentObj instanceof IBaseExtension) { 592 extensionDts = ((IBaseExtension) theCurrentObj).getExtension(); 593 594 if (theAddExtension && (extensionDts.isEmpty() && theSubList.size() == 1)) { 595 extensionDts.add(createEmptyExtensionDt((IBaseExtension) theCurrentObj, extensionUrl)); 596 } 597 598 if (extensionDts.isEmpty() && theCreate) { 599 extensionDts.add(createEmptyExtensionDt((IBaseExtension) theCurrentObj, extensionUrl)); 600 } 601 } 602 603 for (ExtensionDt next : extensionDts) { 604 if (theWantedClass.isAssignableFrom(next.getClass())) { 605 retVal.add((T) next); 606 } 607 } 608 } else { 609 // DSTU3+ 610 final String extensionUrlForLambda = extensionUrl; 611 List<IBaseExtension<?, ?>> extensions = Collections.emptyList(); 612 613 if (theCurrentObj instanceof IBaseHasModifierExtensions) { 614 extensions = ((IBaseHasModifierExtensions) theCurrentObj) 615 .getModifierExtension().stream() 616 .filter(t -> t.getUrl().equals(extensionUrlForLambda)) 617 .collect(Collectors.toList()); 618 619 if (theAddExtension 620 && (!(theCurrentObj instanceof IBaseExtension) 621 || (extensions.isEmpty() && theSubList.size() == 1))) { 622 extensions.add( 623 createEmptyModifierExtension((IBaseHasModifierExtensions) theCurrentObj, extensionUrl)); 624 } 625 626 if (extensions.isEmpty() && theCreate) { 627 extensions.add( 628 createEmptyModifierExtension((IBaseHasModifierExtensions) theCurrentObj, extensionUrl)); 629 } 630 } 631 632 for (IBaseExtension<?, ?> next : extensions) { 633 if (theWantedClass.isAssignableFrom(next.getClass())) { 634 retVal.add((T) next); 635 } 636 } 637 } 638 639 if (theSubList.size() > 1) { 640 List<T> values = retVal; 641 retVal = new ArrayList<>(); 642 for (T nextElement : values) { 643 BaseRuntimeElementCompositeDefinition<?> nextChildDef = (BaseRuntimeElementCompositeDefinition<?>) 644 myContext.getElementDefinition(nextElement.getClass()); 645 List<T> foundValues = getValues( 646 nextChildDef, 647 nextElement, 648 theSubList.subList(1, theSubList.size()), 649 theWantedClass, 650 theCreate, 651 theAddExtension); 652 retVal.addAll(foundValues); 653 } 654 } 655 656 return retVal; 657 } 658 659 BaseRuntimeChildDefinition nextDef = theCurrentDef.getChildByNameOrThrowDataFormatException(name); 660 List<? extends IBase> values = nextDef.getAccessor().getValues(theCurrentObj); 661 662 if (values.isEmpty() && theCreate) { 663 BaseRuntimeElementDefinition<?> childByName = nextDef.getChildByName(name); 664 Object arg = nextDef.getInstanceConstructorArguments(); 665 IBase value; 666 if (arg != null) { 667 value = childByName.newInstance(arg); 668 } else { 669 value = childByName.newInstance(); 670 } 671 nextDef.getMutator().addValue(theCurrentObj, value); 672 List<IBase> list = new ArrayList<>(); 673 list.add(value); 674 values = list; 675 } 676 677 if (theSubList.size() == 1) { 678 if (nextDef instanceof RuntimeChildChoiceDefinition) { 679 for (IBase next : values) { 680 if (next != null) { 681 if (name.endsWith("[x]")) { 682 if (theWantedClass == null || theWantedClass.isAssignableFrom(next.getClass())) { 683 retVal.add((T) next); 684 } 685 } else { 686 String childName = nextDef.getChildNameByDatatype(next.getClass()); 687 if (theSubList.get(0).equals(childName)) { 688 if (theWantedClass == null || theWantedClass.isAssignableFrom(next.getClass())) { 689 retVal.add((T) next); 690 } 691 } 692 } 693 } 694 } 695 } else { 696 for (IBase next : values) { 697 if (next != null) { 698 if (theWantedClass == null || theWantedClass.isAssignableFrom(next.getClass())) { 699 retVal.add((T) next); 700 } 701 } 702 } 703 } 704 } else { 705 for (IBase nextElement : values) { 706 BaseRuntimeElementCompositeDefinition<?> nextChildDef = (BaseRuntimeElementCompositeDefinition<?>) 707 myContext.getElementDefinition(nextElement.getClass()); 708 List<T> foundValues = getValues( 709 nextChildDef, 710 nextElement, 711 theSubList.subList(1, theSubList.size()), 712 theWantedClass, 713 theCreate, 714 theAddExtension); 715 retVal.addAll(foundValues); 716 } 717 } 718 return retVal; 719 } 720 721 /** 722 * Returns values stored in an element identified by its path. The list of values is of 723 * type {@link Object}. 724 * 725 * @param theElement The element to be accessed. Must not be null. 726 * @param thePath The path for the element to be accessed.@param theElement The resource instance to be accessed. Must not be null. 727 * @return A list of values of type {@link Object}. 728 */ 729 public List<IBase> getValues(IBase theElement, String thePath) { 730 Class<IBase> wantedClass = IBase.class; 731 732 return getValues(theElement, thePath, wantedClass); 733 } 734 735 /** 736 * Returns values stored in an element identified by its path. The list of values is of 737 * type {@link Object}. 738 * 739 * @param theElement The element to be accessed. Must not be null. 740 * @param thePath The path for the element to be accessed. 741 * @param theCreate When set to <code>true</code>, the terser will create a null-valued element where none exists. 742 * @return A list of values of type {@link Object}. 743 */ 744 public List<IBase> getValues(IBase theElement, String thePath, boolean theCreate) { 745 Class<IBase> wantedClass = IBase.class; 746 747 return getValues(theElement, thePath, wantedClass, theCreate); 748 } 749 750 /** 751 * Returns values stored in an element identified by its path. The list of values is of 752 * type {@link Object}. 753 * 754 * @param theElement The element to be accessed. Must not be null. 755 * @param thePath The path for the element to be accessed. 756 * @param theCreate When set to <code>true</code>, the terser will create a null-valued element where none exists. 757 * @param theAddExtension When set to <code>true</code>, the terser will add a null-valued extension where one or more such extensions already exist. 758 * @return A list of values of type {@link Object}. 759 */ 760 public List<IBase> getValues(IBase theElement, String thePath, boolean theCreate, boolean theAddExtension) { 761 Class<IBase> wantedClass = IBase.class; 762 763 return getValues(theElement, thePath, wantedClass, theCreate, theAddExtension); 764 } 765 766 /** 767 * Returns values stored in an element identified by its path. The list of values is of 768 * type <code>theWantedClass</code>. 769 * 770 * @param theElement The element to be accessed. Must not be null. 771 * @param thePath The path for the element to be accessed. 772 * @param theWantedClass The desired class to be returned in a list. 773 * @param <T> Type declared by <code>theWantedClass</code> 774 * @return A list of values of type <code>theWantedClass</code>. 775 */ 776 public <T extends IBase> List<T> getValues(IBase theElement, String thePath, Class<T> theWantedClass) { 777 BaseRuntimeElementCompositeDefinition<?> def = 778 (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theElement.getClass()); 779 List<String> parts = parsePath(def, thePath); 780 return getValues(def, theElement, parts, theWantedClass); 781 } 782 783 /** 784 * Returns values stored in an element identified by its path. The list of values is of 785 * type <code>theWantedClass</code>. 786 * 787 * @param theElement The element to be accessed. Must not be null. 788 * @param thePath The path for the element to be accessed. 789 * @param theWantedClass The desired class to be returned in a list. 790 * @param theCreate When set to <code>true</code>, the terser will create a null-valued element where none exists. 791 * @param <T> Type declared by <code>theWantedClass</code> 792 * @return A list of values of type <code>theWantedClass</code>. 793 */ 794 public <T extends IBase> List<T> getValues( 795 IBase theElement, String thePath, Class<T> theWantedClass, boolean theCreate) { 796 BaseRuntimeElementCompositeDefinition<?> def = 797 (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theElement.getClass()); 798 List<String> parts = parsePath(def, thePath); 799 return getValues(def, theElement, parts, theWantedClass, theCreate, false); 800 } 801 802 /** 803 * Returns values stored in an element identified by its path. The list of values is of 804 * type <code>theWantedClass</code>. 805 * 806 * @param theElement The element to be accessed. Must not be null. 807 * @param thePath The path for the element to be accessed. 808 * @param theWantedClass The desired class to be returned in a list. 809 * @param theCreate When set to <code>true</code>, the terser will create a null-valued element where none exists. 810 * @param theAddExtension When set to <code>true</code>, the terser will add a null-valued extension where one or more such extensions already exist. 811 * @param <T> Type declared by <code>theWantedClass</code> 812 * @return A list of values of type <code>theWantedClass</code>. 813 */ 814 public <T extends IBase> List<T> getValues( 815 IBase theElement, String thePath, Class<T> theWantedClass, boolean theCreate, boolean theAddExtension) { 816 BaseRuntimeElementCompositeDefinition<?> def = 817 (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theElement.getClass()); 818 List<String> parts = parsePath(def, thePath); 819 return getValues(def, theElement, parts, theWantedClass, theCreate, theAddExtension); 820 } 821 822 private List<String> parsePath(BaseRuntimeElementCompositeDefinition<?> theElementDef, String thePath) { 823 List<String> parts = new ArrayList<>(); 824 825 int currentStart = 0; 826 boolean inSingleQuote = false; 827 for (int i = 0; i < thePath.length(); i++) { 828 switch (thePath.charAt(i)) { 829 case '\'': 830 inSingleQuote = !inSingleQuote; 831 break; 832 case '.': 833 if (!inSingleQuote) { 834 parts.add(thePath.substring(currentStart, i)); 835 currentStart = i + 1; 836 } 837 break; 838 } 839 } 840 841 parts.add(thePath.substring(currentStart)); 842 843 String firstPart = parts.get(0); 844 if (Character.isUpperCase(firstPart.charAt(0)) && theElementDef instanceof RuntimeResourceDefinition) { 845 if (firstPart.equals(theElementDef.getName())) { 846 parts = parts.subList(1, parts.size()); 847 } else { 848 parts = Collections.emptyList(); 849 return parts; 850 } 851 } else if (firstPart.equals(theElementDef.getName())) { 852 parts = parts.subList(1, parts.size()); 853 } 854 855 if (parts.size() < 1) { 856 throw new ConfigurationException(Msg.code(1792) + "Invalid path: " + thePath); 857 } 858 return parts; 859 } 860 861 /** 862 * Returns <code>true</code> if <code>theSource</code> is in the compartment named <code>theCompartmentName</code> 863 * belonging to resource <code>theTarget</code> 864 * 865 * @param theCompartmentName The name of the compartment 866 * @param theSource The potential member of the compartment 867 * @param theTarget The owner of the compartment. Note that both the resource type and ID must be filled in on this IIdType or the method will throw an {@link IllegalArgumentException} 868 * @return <code>true</code> if <code>theSource</code> is in the compartment 869 * @throws IllegalArgumentException If theTarget does not contain both a resource type and ID 870 */ 871 public boolean isSourceInCompartmentForTarget( 872 String theCompartmentName, IBaseResource theSource, IIdType theTarget) { 873 return isSourceInCompartmentForTarget(theCompartmentName, theSource, theTarget, null); 874 } 875 876 /** 877 * Returns <code>true</code> if <code>theSource</code> is in the compartment named <code>theCompartmentName</code> 878 * belonging to resource <code>theTarget</code> 879 * 880 * @param theCompartmentName The name of the compartment 881 * @param theSource The potential member of the compartment 882 * @param theTarget The owner of the compartment. Note that both the resource type and ID must be filled in on this IIdType or the method will throw an {@link IllegalArgumentException} 883 * @param theAdditionalCompartmentParamNames If provided, search param names provided here will be considered as included in the given compartment for this comparison. 884 * @return <code>true</code> if <code>theSource</code> is in the compartment or one of the additional parameters matched. 885 * @throws IllegalArgumentException If theTarget does not contain both a resource type and ID 886 */ 887 public boolean isSourceInCompartmentForTarget( 888 String theCompartmentName, 889 IBaseResource theSource, 890 IIdType theTarget, 891 @Nullable Set<String> theAdditionalCompartmentParamNames) { 892 Validate.notBlank(theCompartmentName, "theCompartmentName must not be null or blank"); 893 Validate.notNull(theSource, "theSource must not be null"); 894 Validate.notNull(theTarget, "theTarget must not be null"); 895 Validate.notBlank( 896 defaultString(theTarget.getResourceType()), 897 "theTarget must have a populated resource type (theTarget.getResourceType() does not return a value)"); 898 Validate.notBlank( 899 defaultString(theTarget.getIdPart()), 900 "theTarget must have a populated ID (theTarget.getIdPart() does not return a value)"); 901 902 String wantRef = theTarget.toUnqualifiedVersionless().getValue(); 903 904 RuntimeResourceDefinition sourceDef = myContext.getResourceDefinition(theSource); 905 if (theSource.getIdElement().hasIdPart()) { 906 if (wantRef.equals( 907 sourceDef.getName() + '/' + theSource.getIdElement().getIdPart())) { 908 return true; 909 } 910 } 911 912 class CompartmentOwnerVisitor implements ICompartmentOwnerVisitor { 913 914 private final String myWantRef; 915 916 public boolean isFound() { 917 return myFound; 918 } 919 920 private boolean myFound; 921 922 public CompartmentOwnerVisitor(String theWantRef) { 923 myWantRef = theWantRef; 924 } 925 926 @Override 927 public boolean consume(IIdType theCompartmentOwner) { 928 if (myWantRef.equals( 929 theCompartmentOwner.toUnqualifiedVersionless().getValue())) { 930 myFound = true; 931 } 932 return !myFound; 933 } 934 } 935 936 CompartmentOwnerVisitor consumer = new CompartmentOwnerVisitor(wantRef); 937 visitCompartmentOwnersForResource(theCompartmentName, theSource, theAdditionalCompartmentParamNames, consumer); 938 return consumer.isFound(); 939 } 940 941 /** 942 * Returns the owners of the compartment in <code>theSource</code> is in the compartment named <code>theCompartmentName</code>. 943 * 944 * @param theCompartmentName The name of the compartment 945 * @param theSource The potential member of the compartment 946 * @param theAdditionalCompartmentParamNames If provided, search param names provided here will be considered as included in the given compartment for this comparison. 947 */ 948 @Nonnull 949 public List<IIdType> getCompartmentOwnersForResource( 950 String theCompartmentName, IBaseResource theSource, Set<String> theAdditionalCompartmentParamNames) { 951 Validate.notBlank(theCompartmentName, "theCompartmentName must not be null or blank"); 952 Validate.notNull(theSource, "theSource must not be null"); 953 954 class CompartmentOwnerVisitor implements ICompartmentOwnerVisitor { 955 956 private final Set<String> myOwnersAdded = new HashSet<>(); 957 private final List<IIdType> myOwners = new ArrayList<>(2); 958 959 public List<IIdType> getOwners() { 960 return myOwners; 961 } 962 963 @Override 964 public boolean consume(IIdType theCompartmentOwner) { 965 if (myOwnersAdded.add(theCompartmentOwner.getValue())) { 966 myOwners.add(theCompartmentOwner); 967 } 968 return true; 969 } 970 } 971 972 CompartmentOwnerVisitor consumer = new CompartmentOwnerVisitor(); 973 visitCompartmentOwnersForResource(theCompartmentName, theSource, theAdditionalCompartmentParamNames, consumer); 974 return consumer.getOwners(); 975 } 976 977 @Nonnull 978 public Stream<IBaseReference> getCompartmentReferencesForResource( 979 String theCompartmentName, 980 IBaseResource theSource, 981 @Nullable Set<String> theAdditionalCompartmentParamNames) { 982 Validate.notBlank(theCompartmentName, "theCompartmentName must not be null or blank"); 983 Validate.notNull(theSource, "theSource must not be null"); 984 theAdditionalCompartmentParamNames = Objects.requireNonNullElse(theAdditionalCompartmentParamNames, Set.of()); 985 986 RuntimeResourceDefinition sourceDef = myContext.getResourceDefinition(theSource); 987 List<RuntimeSearchParam> params = 988 new ArrayList<>(sourceDef.getSearchParamsForCompartmentName(theCompartmentName)); 989 990 theAdditionalCompartmentParamNames.stream() 991 .map(sourceDef::getSearchParam) 992 .filter(Objects::nonNull) 993 .forEach(params::add); 994 995 return params.stream() 996 .flatMap(nextParam -> nextParam.getPathsSplit().stream()) 997 .filter(StringUtils::isNotBlank) 998 .flatMap(nextPath -> { 999 1000 /* 1001 * DSTU3 and before just defined compartments as being (e.g.) named 1002 * Patient with a path like CarePlan.subject 1003 * 1004 * R4 uses a fancier format like CarePlan.subject.where(resolve() is Patient) 1005 * 1006 * The following Regex is a hack to make that efficient at runtime. 1007 */ 1008 String wantType; 1009 Matcher matcher = COMPARTMENT_MATCHER_PATH.matcher(nextPath); 1010 if (matcher.matches()) { 1011 nextPath = matcher.group(1); 1012 wantType = matcher.group(2); 1013 } else { 1014 wantType = null; 1015 } 1016 1017 return getValues(theSource, nextPath, IBaseReference.class).stream() 1018 .filter(nextValue -> isEmpty(wantType) || wantType.equals(getTypeFromReference(nextValue))); 1019 }); 1020 } 1021 1022 private String getTypeFromReference(IBaseReference theReference) { 1023 1024 IIdType nextTargetId = theReference.getReferenceElement().toUnqualifiedVersionless(); 1025 1026 if (isNotBlank(nextTargetId.getResourceType())) { 1027 return nextTargetId.getResourceType(); 1028 } else if (theReference.getResource() != null) { 1029 /* 1030 * If the reference isn't an explicit resource ID, but instead is just 1031 * a resource object, we'll use that type. 1032 */ 1033 return myContext.getResourceType(theReference.getResource()); 1034 } else { 1035 return "No type on reference"; 1036 } 1037 } 1038 1039 private void visitCompartmentOwnersForResource( 1040 String theCompartmentName, 1041 IBaseResource theSource, 1042 @Nullable Set<String> theAdditionalCompartmentParamNames, 1043 ICompartmentOwnerVisitor theConsumer) { 1044 1045 getCompartmentReferencesForResource(theCompartmentName, theSource, theAdditionalCompartmentParamNames) 1046 .flatMap(nextValue -> { 1047 IIdType nextTargetId = nextValue.getReferenceElement().toUnqualifiedVersionless(); 1048 1049 /* 1050 * If the reference isn't an explicit resource ID, but instead is just 1051 * a resource object, we'll calculate its ID and treat the target 1052 * as that. 1053 */ 1054 if (isBlank(nextTargetId.getValue()) && nextValue.getResource() != null) { 1055 IBaseResource nextTarget = nextValue.getResource(); 1056 nextTargetId = nextTarget.getIdElement().toUnqualifiedVersionless(); 1057 if (!nextTargetId.hasResourceType()) { 1058 String resourceType = myContext.getResourceType(nextTarget); 1059 nextTargetId.setParts(null, resourceType, nextTargetId.getIdPart(), null); 1060 } 1061 } 1062 if (isNotBlank(nextTargetId.getValue())) { 1063 return Stream.of(nextTargetId); 1064 } else { 1065 return Stream.empty(); 1066 } 1067 }) 1068 .takeWhile(theConsumer::consume) 1069 .forEach(nop()); 1070 } 1071 1072 private void visit( 1073 IBase theElement, 1074 BaseRuntimeChildDefinition theChildDefinition, 1075 BaseRuntimeElementDefinition<?> theDefinition, 1076 IModelVisitor2 theCallback, 1077 List<IBase> theContainingElementPath, 1078 List<BaseRuntimeChildDefinition> theChildDefinitionPath, 1079 List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) { 1080 if (theChildDefinition != null) { 1081 theChildDefinitionPath.add(theChildDefinition); 1082 } 1083 theContainingElementPath.add(theElement); 1084 theElementDefinitionPath.add(theDefinition); 1085 1086 boolean recurse = theCallback.acceptElement( 1087 theElement, 1088 Collections.unmodifiableList(theContainingElementPath), 1089 Collections.unmodifiableList(theChildDefinitionPath), 1090 Collections.unmodifiableList(theElementDefinitionPath)); 1091 if (recurse) { 1092 1093 /* 1094 * Visit undeclared extensions 1095 */ 1096 if (theElement instanceof ISupportsUndeclaredExtensions) { 1097 ISupportsUndeclaredExtensions containingElement = (ISupportsUndeclaredExtensions) theElement; 1098 for (ExtensionDt nextExt : containingElement.getUndeclaredExtensions()) { 1099 theContainingElementPath.add(nextExt); 1100 theCallback.acceptUndeclaredExtension( 1101 nextExt, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath); 1102 theContainingElementPath.remove(theContainingElementPath.size() - 1); 1103 } 1104 } 1105 1106 /* 1107 * Now visit the children of the given element 1108 */ 1109 switch (theDefinition.getChildType()) { 1110 case ID_DATATYPE: 1111 case PRIMITIVE_XHTML_HL7ORG: 1112 case PRIMITIVE_XHTML: 1113 case PRIMITIVE_DATATYPE: 1114 // These are primitive types, so we don't need to visit their children 1115 break; 1116 case RESOURCE: 1117 case RESOURCE_BLOCK: 1118 case COMPOSITE_DATATYPE: { 1119 BaseRuntimeElementCompositeDefinition<?> childDef = 1120 (BaseRuntimeElementCompositeDefinition<?>) theDefinition; 1121 for (BaseRuntimeChildDefinition nextChild : childDef.getChildrenAndExtension()) { 1122 List<? extends IBase> values = nextChild.getAccessor().getValues(theElement); 1123 if (values != null) { 1124 for (IBase nextValue : values) { 1125 if (nextValue == null) { 1126 continue; 1127 } 1128 if (nextValue.isEmpty()) { 1129 continue; 1130 } 1131 BaseRuntimeElementDefinition<?> childElementDef; 1132 Class<? extends IBase> valueType = nextValue.getClass(); 1133 childElementDef = nextChild.getChildElementDefinitionByDatatype(valueType); 1134 while (childElementDef == null && IBase.class.isAssignableFrom(valueType)) { 1135 childElementDef = nextChild.getChildElementDefinitionByDatatype(valueType); 1136 valueType = (Class<? extends IBase>) valueType.getSuperclass(); 1137 } 1138 1139 Class<? extends IBase> typeClass = nextValue.getClass(); 1140 while (childElementDef == null && IBase.class.isAssignableFrom(typeClass)) { 1141 //noinspection unchecked 1142 typeClass = (Class<? extends IBase>) typeClass.getSuperclass(); 1143 childElementDef = nextChild.getChildElementDefinitionByDatatype(typeClass); 1144 } 1145 1146 Validate.notNull( 1147 childElementDef, 1148 "Found value of type[%s] which is not valid for field[%s] in %s", 1149 nextValue.getClass(), 1150 nextChild.getElementName(), 1151 childDef.getName()); 1152 1153 visit( 1154 nextValue, 1155 nextChild, 1156 childElementDef, 1157 theCallback, 1158 theContainingElementPath, 1159 theChildDefinitionPath, 1160 theElementDefinitionPath); 1161 } 1162 } 1163 } 1164 break; 1165 } 1166 case CONTAINED_RESOURCES: { 1167 BaseContainedDt value = (BaseContainedDt) theElement; 1168 for (IResource next : value.getContainedResources()) { 1169 BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(next); 1170 visit( 1171 next, 1172 null, 1173 def, 1174 theCallback, 1175 theContainingElementPath, 1176 theChildDefinitionPath, 1177 theElementDefinitionPath); 1178 } 1179 break; 1180 } 1181 case EXTENSION_DECLARED: 1182 case UNDECL_EXT: { 1183 throw new IllegalStateException( 1184 Msg.code(1793) + "state should not happen: " + theDefinition.getChildType()); 1185 } 1186 case CONTAINED_RESOURCE_LIST: { 1187 if (theElement != null) { 1188 BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(theElement.getClass()); 1189 visit( 1190 theElement, 1191 null, 1192 def, 1193 theCallback, 1194 theContainingElementPath, 1195 theChildDefinitionPath, 1196 theElementDefinitionPath); 1197 } 1198 break; 1199 } 1200 } 1201 } 1202 1203 if (theChildDefinition != null) { 1204 theChildDefinitionPath.remove(theChildDefinitionPath.size() - 1); 1205 } 1206 theContainingElementPath.remove(theContainingElementPath.size() - 1); 1207 theElementDefinitionPath.remove(theElementDefinitionPath.size() - 1); 1208 } 1209 1210 /** 1211 * Visit all elements in a given resource 1212 * 1213 * <p> 1214 * Note on scope: This method will descend into any contained resources ({@link IResource#getContained()}) as well, but will not descend into linked resources (e.g. 1215 * {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource) 1216 * </p> 1217 * 1218 * @param theResource The resource to visit 1219 * @param theVisitor The visitor 1220 */ 1221 public void visit(IBaseResource theResource, IModelVisitor theVisitor) { 1222 BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource); 1223 visit(newMap(), theResource, theResource, null, null, def, theVisitor); 1224 } 1225 1226 public Map<Object, Object> newMap() { 1227 return new IdentityHashMap<>(); 1228 } 1229 1230 /** 1231 * Visit all elements in a given resource or element 1232 * <p> 1233 * <b>THIS ALTERNATE METHOD IS STILL EXPERIMENTAL! USE WITH CAUTION</b> 1234 * </p> 1235 * <p> 1236 * Note on scope: This method will descend into any contained resources ({@link IResource#getContained()}) as well, but will not descend into linked resources (e.g. 1237 * {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource) 1238 * </p> 1239 * 1240 * @param theElement The element to visit 1241 * @param theVisitor The visitor 1242 */ 1243 public void visit(IBase theElement, IModelVisitor2 theVisitor) { 1244 BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(theElement.getClass()); 1245 if (def instanceof BaseRuntimeElementCompositeDefinition) { 1246 visit(theElement, null, def, theVisitor, new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); 1247 } else if (theElement instanceof IBaseExtension) { 1248 theVisitor.acceptUndeclaredExtension( 1249 (IBaseExtension<?, ?>) theElement, 1250 Collections.emptyList(), 1251 Collections.emptyList(), 1252 Collections.emptyList()); 1253 } else { 1254 theVisitor.acceptElement( 1255 theElement, Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); 1256 } 1257 } 1258 1259 private void visit( 1260 Map<Object, Object> theStack, 1261 IBaseResource theResource, 1262 IBase theElement, 1263 List<String> thePathToElement, 1264 BaseRuntimeChildDefinition theChildDefinition, 1265 BaseRuntimeElementDefinition<?> theDefinition, 1266 IModelVisitor theCallback) { 1267 List<String> pathToElement = addNameToList(thePathToElement, theChildDefinition); 1268 1269 if (theStack.put(theElement, theElement) != null) { 1270 return; 1271 } 1272 1273 theCallback.acceptElement(theResource, theElement, pathToElement, theChildDefinition, theDefinition); 1274 1275 BaseRuntimeElementDefinition<?> def = theDefinition; 1276 if (def.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST) { 1277 Class<? extends IBase> clazz = theElement.getClass(); 1278 def = myContext.getElementDefinition(clazz); 1279 Validate.notNull(def, "Unable to find element definition for class: %s", clazz); 1280 } 1281 1282 if (theElement instanceof IBaseReference) { 1283 IBaseResource target = ((IBaseReference) theElement).getResource(); 1284 if (target != null) { 1285 if (target.getIdElement().hasIdPart() == false 1286 || target.getIdElement().isLocal()) { 1287 RuntimeResourceDefinition targetDef = myContext.getResourceDefinition(target); 1288 visit(theStack, target, target, pathToElement, null, targetDef, theCallback); 1289 } 1290 } 1291 } 1292 1293 switch (def.getChildType()) { 1294 case ID_DATATYPE: 1295 case PRIMITIVE_XHTML_HL7ORG: 1296 case PRIMITIVE_XHTML: 1297 case PRIMITIVE_DATATYPE: 1298 // These are primitive types 1299 break; 1300 case RESOURCE: 1301 case RESOURCE_BLOCK: 1302 case COMPOSITE_DATATYPE: { 1303 BaseRuntimeElementCompositeDefinition<?> childDef = (BaseRuntimeElementCompositeDefinition<?>) def; 1304 List<BaseRuntimeChildDefinition> childrenAndExtensionDefs = childDef.getChildrenAndExtension(); 1305 for (BaseRuntimeChildDefinition nextChild : childrenAndExtensionDefs) { 1306 1307 List<?> values = nextChild.getAccessor().getValues(theElement); 1308 1309 if (values != null) { 1310 for (Object nextValueObject : values) { 1311 IBase nextValue; 1312 try { 1313 nextValue = (IBase) nextValueObject; 1314 } catch (ClassCastException e) { 1315 String s = "Found instance of " + nextValueObject.getClass() 1316 + " - Did you set a field value to the incorrect type? Expected " 1317 + IBase.class.getName(); 1318 throw new ClassCastException(Msg.code(1794) + s); 1319 } 1320 if (nextValue == null) { 1321 continue; 1322 } 1323 if (nextValue.isEmpty()) { 1324 continue; 1325 } 1326 BaseRuntimeElementDefinition<?> childElementDef; 1327 Class<? extends IBase> clazz = nextValue.getClass(); 1328 childElementDef = nextChild.getChildElementDefinitionByDatatype(clazz); 1329 1330 if (childElementDef == null) { 1331 childElementDef = myContext.getElementDefinition(clazz); 1332 Validate.notNull( 1333 childElementDef, "Unable to find element definition for class: %s", clazz); 1334 } 1335 1336 if (nextChild instanceof RuntimeChildDirectResource) { 1337 // Don't descend into embedded resources 1338 theCallback.acceptElement(theResource, nextValue, null, nextChild, childElementDef); 1339 } else { 1340 visit( 1341 theStack, 1342 theResource, 1343 nextValue, 1344 pathToElement, 1345 nextChild, 1346 childElementDef, 1347 theCallback); 1348 } 1349 } 1350 } 1351 } 1352 break; 1353 } 1354 case CONTAINED_RESOURCES: { 1355 BaseContainedDt value = (BaseContainedDt) theElement; 1356 for (IResource next : value.getContainedResources()) { 1357 def = myContext.getResourceDefinition(next); 1358 visit(theStack, next, next, pathToElement, null, def, theCallback); 1359 } 1360 break; 1361 } 1362 case CONTAINED_RESOURCE_LIST: 1363 case EXTENSION_DECLARED: 1364 case UNDECL_EXT: { 1365 throw new IllegalStateException(Msg.code(1795) + "state should not happen: " + def.getChildType()); 1366 } 1367 } 1368 1369 theStack.remove(theElement); 1370 } 1371 1372 /** 1373 * Returns all embedded resources that are found embedded within <code>theResource</code>. 1374 * An embedded resource is a resource that can be found as a direct child within a resource, 1375 * as opposed to being referenced by the resource. 1376 * <p> 1377 * Examples include resources found within <code>Bundle.entry.resource</code> 1378 * and <code>Parameters.parameter.resource</code>, as well as contained resources 1379 * found within <code>Resource.contained</code> 1380 * </p> 1381 * 1382 * @param theRecurse Should embedded resources be recursively scanned for further embedded 1383 * resources 1384 * @return A collection containing the embedded resources. Order is arbitrary. 1385 */ 1386 public Collection<IBaseResource> getAllEmbeddedResources(IBaseResource theResource, boolean theRecurse) { 1387 Validate.notNull(theResource, "theResource must not be null"); 1388 ArrayList<IBaseResource> retVal = new ArrayList<>(); 1389 1390 visit(theResource, new IModelVisitor2() { 1391 @Override 1392 public boolean acceptElement( 1393 IBase theElement, 1394 List<IBase> theContainingElementPath, 1395 List<BaseRuntimeChildDefinition> theChildDefinitionPath, 1396 List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) { 1397 if (theElement == theResource) { 1398 return true; 1399 } 1400 if (theElement instanceof IBaseResource) { 1401 retVal.add((IBaseResource) theElement); 1402 return theRecurse; 1403 } 1404 return true; 1405 } 1406 1407 @Override 1408 public boolean acceptUndeclaredExtension( 1409 IBaseExtension<?, ?> theNextExt, 1410 List<IBase> theContainingElementPath, 1411 List<BaseRuntimeChildDefinition> theChildDefinitionPath, 1412 List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) { 1413 return true; 1414 } 1415 }); 1416 1417 return retVal; 1418 } 1419 1420 /** 1421 * Clear all content on a resource 1422 */ 1423 public void clear(IBaseResource theInput) { 1424 visit(theInput, new IModelVisitor2() { 1425 @Override 1426 public boolean acceptElement( 1427 IBase theElement, 1428 List<IBase> theContainingElementPath, 1429 List<BaseRuntimeChildDefinition> theChildDefinitionPath, 1430 List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) { 1431 if (theElement instanceof IPrimitiveType) { 1432 ((IPrimitiveType) theElement).setValueAsString(null); 1433 } 1434 return true; 1435 } 1436 1437 @Override 1438 public boolean acceptUndeclaredExtension( 1439 IBaseExtension<?, ?> theNextExt, 1440 List<IBase> theContainingElementPath, 1441 List<BaseRuntimeChildDefinition> theChildDefinitionPath, 1442 List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) { 1443 theNextExt.setUrl(null); 1444 theNextExt.setValue(null); 1445 return true; 1446 } 1447 }); 1448 } 1449 1450 private void containResourcesForEncoding(ContainedResources theContained, IBaseResource theResource) { 1451 List<IBaseReference> allReferences = getAllPopulatedChildElementsOfType(theResource, IBaseReference.class); 1452 1453 // Note that we process all contained resources that have arrived here with an ID contained resources first, so 1454 // that we don't accidentally auto-assign an ID 1455 // which may collide with a resource we have yet to process. 1456 // See: https://github.com/hapifhir/hapi-fhir/issues/6403 1457 allReferences.sort(REFERENCES_WITH_IDS_FIRST); 1458 1459 for (IBaseReference next : allReferences) { 1460 IBaseResource resource = next.getResource(); 1461 if (resource == null && next.getReferenceElement().isLocal()) { 1462 if (theContained.hasExistingIdToContainedResource()) { 1463 IBaseResource potentialTarget = theContained 1464 .getExistingIdToContainedResource() 1465 .remove(next.getReferenceElement().getValue()); 1466 if (potentialTarget != null) { 1467 theContained.addContained(next.getReferenceElement(), potentialTarget); 1468 containResourcesForEncoding(theContained, potentialTarget); 1469 } 1470 } 1471 } 1472 } 1473 1474 for (IBaseReference next : allReferences) { 1475 IBaseResource resource = next.getResource(); 1476 if (resource != null) { 1477 if (resource.getIdElement().isEmpty() || resource.getIdElement().isLocal()) { 1478 1479 IIdType id = theContained.addContained(resource); 1480 if (id == null) { 1481 continue; 1482 } 1483 getContainedResourceList(theResource).add(resource); 1484 1485 String idString = id.getValue(); 1486 if (!idString.startsWith("#")) { 1487 idString = "#" + idString; 1488 } 1489 1490 next.setReference(idString); 1491 next.setResource(null); 1492 if (resource.getIdElement().isLocal() && theContained.hasExistingIdToContainedResource()) { 1493 theContained 1494 .getExistingIdToContainedResource() 1495 .remove(resource.getIdElement().getValue()); 1496 } 1497 } else { 1498 IIdType previouslyContainedResourceId = theContained.getPreviouslyContainedResourceId(resource); 1499 if (previouslyContainedResourceId != null) { 1500 if (theContained.getResourceId(resource) == null) { 1501 theContained.addContained(previouslyContainedResourceId, resource); 1502 getContainedResourceList(theResource).add(resource); 1503 } 1504 } 1505 } 1506 } 1507 } 1508 } 1509 1510 /** 1511 * Iterate through the whole resource and identify any contained resources. Optionally this method 1512 * can also assign IDs and modify references where the resource link has been specified but not the 1513 * reference text. 1514 * 1515 * @since 5.4.0 1516 */ 1517 public ContainedResources containResources( 1518 IBaseResource theResource, ContainedResources theParentContainedResources, boolean theStoreResults) { 1519 1520 if (theStoreResults) { 1521 Object cachedValue = theResource.getUserData(USER_DATA_KEY_CONTAIN_RESOURCES_COMPLETED); 1522 if (cachedValue != null) { 1523 return (ContainedResources) cachedValue; 1524 } 1525 } 1526 1527 ContainedResources contained = new ContainedResources(); 1528 1529 List<? extends IBaseResource> containedResources = getContainedResourceList(theResource); 1530 for (IBaseResource next : containedResources) { 1531 String nextId = next.getIdElement().getValue(); 1532 if (StringUtils.isNotBlank(nextId)) { 1533 while (nextId.startsWith("#")) { 1534 ourLog.warn( 1535 "Found contained resource with ID starting with # character ({}). This form of ID is deprecated and will be dropped in a future release, preventing the current code from working correctly.", 1536 nextId); 1537 nextId = nextId.substring(1); 1538 } 1539 next.getIdElement().setValue(nextId); 1540 } 1541 contained.addContained(next); 1542 } 1543 1544 if (myContext.getParserOptions().isAutoContainReferenceTargetsWithNoId()) { 1545 if (theParentContainedResources != null) { 1546 if (theParentContainedResources.hasResourceToIdValues()) { 1547 contained 1548 .getPreviouslyContainedResourceToIdMap() 1549 .putAll(theParentContainedResources.getResourceToIdMap()); 1550 } 1551 if (theParentContainedResources.hasPreviouslyContainedResourceToIdValues()) { 1552 contained 1553 .getPreviouslyContainedResourceToIdMap() 1554 .putAll(theParentContainedResources.getPreviouslyContainedResourceToIdMap()); 1555 } 1556 } 1557 1558 containResourcesForEncoding(contained, theResource); 1559 } 1560 1561 if (theStoreResults) { 1562 theResource.setUserData(USER_DATA_KEY_CONTAIN_RESOURCES_COMPLETED, contained); 1563 } 1564 1565 return contained; 1566 } 1567 1568 @SuppressWarnings("unchecked") 1569 private <T extends IBaseResource> List<T> getContainedResourceList(T theResource) { 1570 List<T> containedResources = Collections.emptyList(); 1571 if (theResource instanceof IResource) { 1572 containedResources = 1573 (List<T>) ((IResource) theResource).getContained().getContainedResources(); 1574 } else if (theResource instanceof IDomainResource) { 1575 containedResources = (List<T>) ((IDomainResource) theResource).getContained(); 1576 } 1577 return containedResources; 1578 } 1579 1580 /** 1581 * Adds and returns a new element at the given path within the given structure. The paths used here 1582 * are <b>not FHIRPath expressions</b> but instead just simple dot-separated path expressions. 1583 * <p> 1584 * Only the last entry in the path is always created, existing repetitions of elements before 1585 * the final dot are returned if they exists (although they are created if they do not). For example, 1586 * given the path <code>Patient.name.given</code>, a new repetition of <code>given</code> is always 1587 * added to the first (index 0) repetition of the name. If an index-0 repetition of <code>name</code> 1588 * already exists, it is added to. If one does not exist, it if created and then added to. 1589 * </p> 1590 * <p> 1591 * If the last element in the path refers to a non-repeatable element that is already present and 1592 * is not empty, a {@link DataFormatException} error will be thrown. 1593 * </p> 1594 * 1595 * @param theTarget The element to add to. This will often be a {@link IBaseResource resource} 1596 * instance, but does not need to be. 1597 * @param thePath The path. 1598 * @return The newly added element 1599 * @throws DataFormatException If the path is invalid or does not end with either a repeatable element, or 1600 * an element that is non-repeatable but not already populated. 1601 */ 1602 @SuppressWarnings("unchecked") 1603 @Nonnull 1604 public <T extends IBase> T addElement(@Nonnull IBase theTarget, @Nonnull String thePath) { 1605 return (T) doAddElement(theTarget, thePath, 1).get(0); 1606 } 1607 1608 @SuppressWarnings("unchecked") 1609 private <T extends IBase> List<T> doAddElement(IBase theTarget, String thePath, int theElementsToAdd) { 1610 if (theElementsToAdd == 0) { 1611 return Collections.emptyList(); 1612 } 1613 1614 IBase target = theTarget; 1615 BaseRuntimeElementCompositeDefinition<?> def = 1616 (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(target.getClass()); 1617 List<String> parts = parsePath(def, thePath); 1618 1619 for (int i = 0, partsSize = parts.size(); ; i++) { 1620 String nextPart = parts.get(i); 1621 boolean lastPart = i == partsSize - 1; 1622 1623 BaseRuntimeChildDefinition nextChild = def.getChildByName(nextPart); 1624 if (nextChild == null) { 1625 throw new DataFormatException(Msg.code(1796) + "Invalid path " + thePath + ": Element of type " 1626 + def.getName() + " has no child named " + nextPart + ". Valid names: " 1627 + def.getChildrenAndExtension().stream() 1628 .map(BaseRuntimeChildDefinition::getElementName) 1629 .sorted() 1630 .collect(Collectors.joining(", "))); 1631 } 1632 1633 List<IBase> childValues = nextChild.getAccessor().getValues(target); 1634 IBase childValue; 1635 if (childValues.size() > 0 && !lastPart) { 1636 childValue = childValues.get(0); 1637 } else { 1638 1639 if (lastPart) { 1640 if (!childValues.isEmpty()) { 1641 if (theElementsToAdd == -1) { 1642 return (List<T>) Collections.singletonList(childValues.get(0)); 1643 } else if (nextChild.getMax() == 1 1644 && !childValues.get(0).isEmpty()) { 1645 throw new DataFormatException( 1646 Msg.code(1797) + "Element at path " + thePath + " is not repeatable and not empty"); 1647 } else if (nextChild.getMax() == 1 && childValues.get(0).isEmpty()) { 1648 return (List<T>) Collections.singletonList(childValues.get(0)); 1649 } 1650 } 1651 } 1652 1653 BaseRuntimeElementDefinition<?> elementDef = nextChild.getChildByName(nextPart); 1654 childValue = elementDef.newInstance(nextChild.getInstanceConstructorArguments()); 1655 nextChild.getMutator().addValue(target, childValue); 1656 1657 if (lastPart) { 1658 if (theElementsToAdd == 1 || theElementsToAdd == -1) { 1659 return (List<T>) Collections.singletonList(childValue); 1660 } else { 1661 if (nextChild.getMax() == 1) { 1662 throw new DataFormatException(Msg.code(1798) + "Can not add multiple values at path " 1663 + thePath + ": Element does not repeat"); 1664 } 1665 1666 List<T> values = (List<T>) Lists.newArrayList(childValue); 1667 for (int j = 1; j < theElementsToAdd; j++) { 1668 childValue = elementDef.newInstance(nextChild.getInstanceConstructorArguments()); 1669 nextChild.getMutator().addValue(target, childValue); 1670 values.add((T) childValue); 1671 } 1672 1673 return values; 1674 } 1675 } 1676 } 1677 1678 target = childValue; 1679 1680 if (!lastPart) { 1681 BaseRuntimeElementDefinition<?> nextDef = myContext.getElementDefinition(target.getClass()); 1682 if (!(nextDef instanceof BaseRuntimeElementCompositeDefinition)) { 1683 throw new DataFormatException(Msg.code(1799) + "Invalid path " + thePath + ": Element of type " 1684 + def.getName() + " has no child named " + nextPart + " (this is a primitive type)"); 1685 } 1686 def = (BaseRuntimeElementCompositeDefinition<?>) nextDef; 1687 } 1688 } 1689 } 1690 1691 /** 1692 * Adds and returns a new element at the given path within the given structure. The paths used here 1693 * are <b>not FHIRPath expressions</b> but instead just simple dot-separated path expressions. 1694 * <p> 1695 * This method follows all of the same semantics as {@link #addElement(IBase, String)} but it 1696 * requires the path to point to an element with a primitive datatype and set the value of 1697 * the datatype to the given value. 1698 * </p> 1699 * 1700 * @param theTarget The element to add to. This will often be a {@link IBaseResource resource} 1701 * instance, but does not need to be. 1702 * @param thePath The path. 1703 * @param theValue The value to set, or <code>null</code>. 1704 * @return The newly added element 1705 * @throws DataFormatException If the path is invalid or does not end with either a repeatable element, or 1706 * an element that is non-repeatable but not already populated. 1707 */ 1708 @SuppressWarnings("unchecked") 1709 @Nonnull 1710 public <T extends IBase> T addElement( 1711 @Nonnull IBase theTarget, @Nonnull String thePath, @Nullable String theValue) { 1712 T value = (T) doAddElement(theTarget, thePath, 1).get(0); 1713 if (!(value instanceof IPrimitiveType)) { 1714 throw new DataFormatException( 1715 Msg.code(1800) + "Element at path " + thePath + " is not a primitive datatype. Found: " 1716 + myContext.getElementDefinition(value.getClass()).getName()); 1717 } 1718 1719 ((IPrimitiveType<?>) value).setValueAsString(theValue); 1720 1721 return value; 1722 } 1723 1724 /** 1725 * Adds and returns a new element at the given path within the given structure. The paths used here 1726 * are <b>not FHIRPath expressions</b> but instead just simple dot-separated path expressions. 1727 * <p> 1728 * This method follows all of the same semantics as {@link #addElement(IBase, String)} but it 1729 * requires the path to point to an element with a primitive datatype and set the value of 1730 * the datatype to the given value. 1731 * </p> 1732 * 1733 * @param theTarget The element to add to. This will often be a {@link IBaseResource resource} 1734 * instance, but does not need to be. 1735 * @param thePath The path. 1736 * @param theValue The value to set, or <code>null</code>. 1737 * @return The newly added element 1738 * @throws DataFormatException If the path is invalid or does not end with either a repeatable element, or 1739 * an element that is non-repeatable but not already populated. 1740 */ 1741 @SuppressWarnings("unchecked") 1742 @Nonnull 1743 public <T extends IBase> T setElement( 1744 @Nonnull IBase theTarget, @Nonnull String thePath, @Nullable String theValue) { 1745 T value = (T) doAddElement(theTarget, thePath, -1).get(0); 1746 if (!(value instanceof IPrimitiveType)) { 1747 throw new DataFormatException( 1748 Msg.code(1801) + "Element at path " + thePath + " is not a primitive datatype. Found: " 1749 + myContext.getElementDefinition(value.getClass()).getName()); 1750 } 1751 1752 ((IPrimitiveType<?>) value).setValueAsString(theValue); 1753 1754 return value; 1755 } 1756 1757 /** 1758 * This method has the same semantics as {@link #addElement(IBase, String, String)} but adds 1759 * a collection of primitives instead of a single one. 1760 * 1761 * @param theTarget The element to add to. This will often be a {@link IBaseResource resource} 1762 * instance, but does not need to be. 1763 * @param thePath The path. 1764 * @param theValues The values to set, or <code>null</code>. 1765 */ 1766 public void addElements(IBase theTarget, String thePath, Collection<String> theValues) { 1767 List<IBase> targets = doAddElement(theTarget, thePath, theValues.size()); 1768 Iterator<String> valuesIter = theValues.iterator(); 1769 for (IBase target : targets) { 1770 1771 if (!(target instanceof IPrimitiveType)) { 1772 throw new DataFormatException(Msg.code(1802) + "Element at path " + thePath 1773 + " is not a primitive datatype. Found: " 1774 + myContext.getElementDefinition(target.getClass()).getName()); 1775 } 1776 1777 ((IPrimitiveType<?>) target).setValueAsString(valuesIter.next()); 1778 } 1779 } 1780 1781 /** 1782 * Clones a resource object, copying all data elements from theSource into a new copy of the same type. 1783 * <p> 1784 * Note that: 1785 * <ul> 1786 * <li>Only FHIR data elements are copied (i.e. user data maps are not copied)</li> 1787 * <li>If a class extending a HAPI FHIR type (e.g. an instance of a class extending the Patient class) is supplied, an instance of the base type will be returned.</li> 1788 * </ul> 1789 * 1790 * @param theSource The source resource 1791 * @return A copy of the source resource 1792 * @since 5.6.0 1793 */ 1794 @SuppressWarnings("unchecked") 1795 public <T extends IBaseResource> T clone(T theSource) { 1796 Validate.notNull(theSource, "theSource must not be null"); 1797 T target = (T) myContext.getResourceDefinition(theSource).newInstance(); 1798 cloneInto(theSource, target, false); 1799 return target; 1800 } 1801 1802 @FunctionalInterface 1803 private interface ICompartmentOwnerVisitor { 1804 1805 /** 1806 * @return Returns true if we should keep looking for more 1807 */ 1808 boolean consume(IIdType theCompartmentOwner); 1809 } 1810 1811 public static class ContainedResources { 1812 private List<IBaseResource> myResourceList; 1813 private IdentityHashMap<IBaseResource, IIdType> myResourceToIdMap; 1814 private IdentityHashMap<IBaseResource, IIdType> myPreviouslyContainedResourceToIdMap; 1815 private Map<String, IBaseResource> myExistingIdToContainedResourceMap; 1816 1817 public ContainedResources() { 1818 super(); 1819 } 1820 1821 public Map<String, IBaseResource> getExistingIdToContainedResource() { 1822 if (myExistingIdToContainedResourceMap == null) { 1823 myExistingIdToContainedResourceMap = new HashMap<>(); 1824 } 1825 return myExistingIdToContainedResourceMap; 1826 } 1827 1828 public IIdType addContained(IBaseResource theResource) { 1829 if (this.getResourceId(theResource) != null) { 1830 // Prevent infinite recursion if there are circular loops in the contained resources 1831 return null; 1832 } 1833 1834 IIdType existing = getResourceToIdMap().get(theResource); 1835 if (existing != null) { 1836 return existing; 1837 } 1838 1839 IIdType newId = theResource.getIdElement(); 1840 if (isBlank(newId.getValue())) { 1841 newId.setValue(UUID.randomUUID().toString()); 1842 } 1843 1844 getResourceToIdMap().put(theResource, newId); 1845 getOrCreateResourceList().add(theResource); 1846 return newId; 1847 } 1848 1849 public void addContained(IIdType theId, IBaseResource theResource) { 1850 if (!getResourceToIdMap().containsKey(theResource)) { 1851 getResourceToIdMap().put(theResource, theId); 1852 getOrCreateResourceList().add(theResource); 1853 } 1854 } 1855 1856 public List<IBaseResource> getContainedResources() { 1857 if (getResourceToIdMap() == null) { 1858 return Collections.emptyList(); 1859 } 1860 return getOrCreateResourceList(); 1861 } 1862 1863 public IIdType getResourceId(IBaseResource theNext) { 1864 if (getResourceToIdMap() == null) { 1865 return null; 1866 } 1867 1868 var idFromMap = getResourceToIdMap().get(theNext); 1869 if (idFromMap != null) { 1870 return idFromMap; 1871 } else if (theNext.getIdElement().getIdPart() != null) { 1872 return getResourceToIdMap().values().stream() 1873 .filter(id -> theNext.getIdElement().getIdPart().equals(id.getIdPart())) 1874 .findAny() 1875 .orElse(null); 1876 } else { 1877 return null; 1878 } 1879 } 1880 1881 private List<IBaseResource> getOrCreateResourceList() { 1882 if (myResourceList == null) { 1883 myResourceList = new ArrayList<>(); 1884 } 1885 return myResourceList; 1886 } 1887 1888 private boolean hasResourceToIdValues() { 1889 return myResourceToIdMap != null && !myResourceToIdMap.isEmpty(); 1890 } 1891 1892 private IdentityHashMap<IBaseResource, IIdType> getResourceToIdMap() { 1893 if (myResourceToIdMap == null) { 1894 myResourceToIdMap = new IdentityHashMap<>(); 1895 } 1896 return myResourceToIdMap; 1897 } 1898 1899 public boolean isEmpty() { 1900 if (myResourceToIdMap == null) { 1901 return true; 1902 } 1903 return myResourceToIdMap.isEmpty(); 1904 } 1905 1906 public boolean hasExistingIdToContainedResource() { 1907 return myExistingIdToContainedResourceMap != null; 1908 } 1909 1910 public IdentityHashMap<IBaseResource, IIdType> getPreviouslyContainedResourceToIdMap() { 1911 if (myPreviouslyContainedResourceToIdMap == null) { 1912 myPreviouslyContainedResourceToIdMap = new IdentityHashMap<>(); 1913 } 1914 return myPreviouslyContainedResourceToIdMap; 1915 } 1916 1917 public boolean hasPreviouslyContainedResourceToIdValues() { 1918 return myPreviouslyContainedResourceToIdMap != null && !myPreviouslyContainedResourceToIdMap.isEmpty(); 1919 } 1920 1921 public IIdType getPreviouslyContainedResourceId(IBaseResource theResource) { 1922 if (hasPreviouslyContainedResourceToIdValues()) { 1923 return getPreviouslyContainedResourceToIdMap().get(theResource); 1924 } 1925 return null; 1926 } 1927 } 1928}