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