
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 // We can only continue iterating if the current element is composite. 707 // If we haven't reached the end of the path, and we have already reached an element 708 // with a primitive data type, we did not find a match. 709 if (myContext.getElementDefinition(nextElement.getClass()) 710 instanceof BaseRuntimeElementCompositeDefinition<?> nextChildDef) { 711 List<T> foundValues = getValues( 712 nextChildDef, 713 nextElement, 714 theSubList.subList(1, theSubList.size()), 715 theWantedClass, 716 theCreate, 717 theAddExtension); 718 retVal.addAll(foundValues); 719 } 720 } 721 } 722 return retVal; 723 } 724 725 /** 726 * Returns values stored in an element identified by its path. The list of values is of 727 * type {@link Object}. 728 * 729 * @param theElement The element to be accessed. Must not be null. 730 * @param thePath The path for the element to be accessed.@param theElement The resource instance to be accessed. Must not be null. 731 * @return A list of values of type {@link Object}. 732 */ 733 public List<IBase> getValues(IBase theElement, String thePath) { 734 Class<IBase> wantedClass = IBase.class; 735 736 return getValues(theElement, thePath, wantedClass); 737 } 738 739 /** 740 * Returns values stored in an element identified by its path. The list of values is of 741 * type {@link Object}. 742 * 743 * @param theElement The element to be accessed. Must not be null. 744 * @param thePath The path for the element to be accessed. 745 * @param theCreate When set to <code>true</code>, the terser will create a null-valued element where none exists. 746 * @return A list of values of type {@link Object}. 747 */ 748 public List<IBase> getValues(IBase theElement, String thePath, boolean theCreate) { 749 Class<IBase> wantedClass = IBase.class; 750 751 return getValues(theElement, thePath, wantedClass, theCreate); 752 } 753 754 /** 755 * Returns values stored in an element identified by its path. The list of values is of 756 * type {@link Object}. 757 * 758 * @param theElement The element to be accessed. Must not be null. 759 * @param thePath The path for the element to be accessed. 760 * @param theCreate When set to <code>true</code>, the terser will create a null-valued element where none exists. 761 * @param theAddExtension When set to <code>true</code>, the terser will add a null-valued extension where one or more such extensions already exist. 762 * @return A list of values of type {@link Object}. 763 */ 764 public List<IBase> getValues(IBase theElement, String thePath, boolean theCreate, boolean theAddExtension) { 765 Class<IBase> wantedClass = IBase.class; 766 767 return getValues(theElement, thePath, wantedClass, theCreate, theAddExtension); 768 } 769 770 /** 771 * Returns values stored in an element identified by its path. The list of values is of 772 * type <code>theWantedClass</code>. 773 * 774 * @param theElement The element to be accessed. Must not be null. 775 * @param thePath The path for the element to be accessed. 776 * @param theWantedClass The desired class to be returned in a list. 777 * @param <T> Type declared by <code>theWantedClass</code> 778 * @return A list of values of type <code>theWantedClass</code>. 779 */ 780 public <T extends IBase> List<T> getValues(IBase theElement, String thePath, Class<T> theWantedClass) { 781 BaseRuntimeElementCompositeDefinition<?> def = 782 (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theElement.getClass()); 783 List<String> parts = parsePath(def, thePath); 784 return getValues(def, theElement, parts, theWantedClass); 785 } 786 787 /** 788 * Returns values stored in an element identified by its path. The list of values is of 789 * type <code>theWantedClass</code>. 790 * 791 * @param theElement The element to be accessed. Must not be null. 792 * @param thePath The path for the element to be accessed. 793 * @param theWantedClass The desired class to be returned in a list. 794 * @param theCreate When set to <code>true</code>, the terser will create a null-valued element where none exists. 795 * @param <T> Type declared by <code>theWantedClass</code> 796 * @return A list of values of type <code>theWantedClass</code>. 797 */ 798 public <T extends IBase> List<T> getValues( 799 IBase theElement, String thePath, Class<T> theWantedClass, boolean theCreate) { 800 BaseRuntimeElementCompositeDefinition<?> def = 801 (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theElement.getClass()); 802 List<String> parts = parsePath(def, thePath); 803 return getValues(def, theElement, parts, theWantedClass, theCreate, false); 804 } 805 806 /** 807 * Returns values stored in an element identified by its path. The list of values is of 808 * type <code>theWantedClass</code>. 809 * 810 * @param theElement The element to be accessed. Must not be null. 811 * @param thePath The path for the element to be accessed. 812 * @param theWantedClass The desired class to be returned in a list. 813 * @param theCreate When set to <code>true</code>, the terser will create a null-valued element where none exists. 814 * @param theAddExtension When set to <code>true</code>, the terser will add a null-valued extension where one or more such extensions already exist. 815 * @param <T> Type declared by <code>theWantedClass</code> 816 * @return A list of values of type <code>theWantedClass</code>. 817 */ 818 public <T extends IBase> List<T> getValues( 819 IBase theElement, String thePath, Class<T> theWantedClass, boolean theCreate, boolean theAddExtension) { 820 BaseRuntimeElementCompositeDefinition<?> def = 821 (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theElement.getClass()); 822 List<String> parts = parsePath(def, thePath); 823 return getValues(def, theElement, parts, theWantedClass, theCreate, theAddExtension); 824 } 825 826 private List<String> parsePath(BaseRuntimeElementCompositeDefinition<?> theElementDef, String thePath) { 827 List<String> parts = new ArrayList<>(); 828 829 int currentStart = 0; 830 boolean inSingleQuote = false; 831 for (int i = 0; i < thePath.length(); i++) { 832 switch (thePath.charAt(i)) { 833 case '\'': 834 inSingleQuote = !inSingleQuote; 835 break; 836 case '.': 837 if (!inSingleQuote) { 838 parts.add(thePath.substring(currentStart, i)); 839 currentStart = i + 1; 840 } 841 break; 842 } 843 } 844 845 parts.add(thePath.substring(currentStart)); 846 847 String firstPart = parts.get(0); 848 if (Character.isUpperCase(firstPart.charAt(0)) && theElementDef instanceof RuntimeResourceDefinition) { 849 if (firstPart.equals(theElementDef.getName())) { 850 parts = parts.subList(1, parts.size()); 851 } else { 852 parts = Collections.emptyList(); 853 return parts; 854 } 855 } else if (firstPart.equals(theElementDef.getName())) { 856 parts = parts.subList(1, parts.size()); 857 } 858 859 if (parts.size() < 1) { 860 throw new ConfigurationException(Msg.code(1792) + "Invalid path: " + thePath); 861 } 862 return parts; 863 } 864 865 /** 866 * Returns <code>true</code> if <code>theSource</code> is in the compartment named <code>theCompartmentName</code> 867 * belonging to resource <code>theTarget</code> 868 * 869 * @param theCompartmentName The name of the compartment 870 * @param theSource The potential member of the compartment 871 * @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} 872 * @return <code>true</code> if <code>theSource</code> is in the compartment 873 * @throws IllegalArgumentException If theTarget does not contain both a resource type and ID 874 */ 875 public boolean isSourceInCompartmentForTarget( 876 String theCompartmentName, IBaseResource theSource, IIdType theTarget) { 877 return isSourceInCompartmentForTarget(theCompartmentName, theSource, theTarget, null); 878 } 879 880 /** 881 * Returns <code>true</code> if <code>theSource</code> is in the compartment named <code>theCompartmentName</code> 882 * belonging to resource <code>theTarget</code> 883 * 884 * @param theCompartmentName The name of the compartment 885 * @param theSource The potential member of the compartment 886 * @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} 887 * @param theAdditionalCompartmentParamNames If provided, search param names provided here will be considered as included in the given compartment for this comparison. 888 * @return <code>true</code> if <code>theSource</code> is in the compartment or one of the additional parameters matched. 889 * @throws IllegalArgumentException If theTarget does not contain both a resource type and ID 890 */ 891 public boolean isSourceInCompartmentForTarget( 892 String theCompartmentName, 893 IBaseResource theSource, 894 IIdType theTarget, 895 @Nullable Set<String> theAdditionalCompartmentParamNames) { 896 Validate.notBlank(theCompartmentName, "theCompartmentName must not be null or blank"); 897 Validate.notNull(theSource, "theSource must not be null"); 898 Validate.notNull(theTarget, "theTarget must not be null"); 899 Validate.notBlank( 900 defaultString(theTarget.getResourceType()), 901 "theTarget must have a populated resource type (theTarget.getResourceType() does not return a value)"); 902 Validate.notBlank( 903 defaultString(theTarget.getIdPart()), 904 "theTarget must have a populated ID (theTarget.getIdPart() does not return a value)"); 905 906 String wantRef = theTarget.toUnqualifiedVersionless().getValue(); 907 908 RuntimeResourceDefinition sourceDef = myContext.getResourceDefinition(theSource); 909 if (theSource.getIdElement().hasIdPart()) { 910 if (wantRef.equals( 911 sourceDef.getName() + '/' + theSource.getIdElement().getIdPart())) { 912 return true; 913 } 914 } 915 916 class CompartmentOwnerVisitor implements ICompartmentOwnerVisitor { 917 918 private final String myWantRef; 919 920 public boolean isFound() { 921 return myFound; 922 } 923 924 private boolean myFound; 925 926 public CompartmentOwnerVisitor(String theWantRef) { 927 myWantRef = theWantRef; 928 } 929 930 @Override 931 public boolean consume(IIdType theCompartmentOwner) { 932 if (myWantRef.equals( 933 theCompartmentOwner.toUnqualifiedVersionless().getValue())) { 934 myFound = true; 935 } 936 return !myFound; 937 } 938 } 939 940 CompartmentOwnerVisitor consumer = new CompartmentOwnerVisitor(wantRef); 941 visitCompartmentOwnersForResource(theCompartmentName, theSource, theAdditionalCompartmentParamNames, consumer); 942 return consumer.isFound(); 943 } 944 945 /** 946 * Returns the owners of the compartment in <code>theSource</code> is in the compartment named <code>theCompartmentName</code>. 947 * 948 * @param theCompartmentName The name of the compartment 949 * @param theSource The potential member of the compartment 950 * @param theAdditionalCompartmentParamNames If provided, search param names provided here will be considered as included in the given compartment for this comparison. 951 */ 952 @Nonnull 953 public List<IIdType> getCompartmentOwnersForResource( 954 String theCompartmentName, IBaseResource theSource, Set<String> theAdditionalCompartmentParamNames) { 955 Validate.notBlank(theCompartmentName, "theCompartmentName must not be null or blank"); 956 Validate.notNull(theSource, "theSource must not be null"); 957 958 class CompartmentOwnerVisitor implements ICompartmentOwnerVisitor { 959 960 private final Set<String> myOwnersAdded = new HashSet<>(); 961 private final List<IIdType> myOwners = new ArrayList<>(2); 962 963 public List<IIdType> getOwners() { 964 return myOwners; 965 } 966 967 @Override 968 public boolean consume(IIdType theCompartmentOwner) { 969 if (myOwnersAdded.add(theCompartmentOwner.getValue())) { 970 myOwners.add(theCompartmentOwner); 971 } 972 return true; 973 } 974 } 975 976 CompartmentOwnerVisitor consumer = new CompartmentOwnerVisitor(); 977 visitCompartmentOwnersForResource(theCompartmentName, theSource, theAdditionalCompartmentParamNames, consumer); 978 return consumer.getOwners(); 979 } 980 981 @Nonnull 982 public Stream<IBaseReference> getCompartmentReferencesForResource( 983 String theCompartmentName, 984 IBaseResource theSource, 985 @Nullable Set<String> theAdditionalCompartmentParamNames) { 986 Validate.notBlank(theCompartmentName, "theCompartmentName must not be null or blank"); 987 Validate.notNull(theSource, "theSource must not be null"); 988 theAdditionalCompartmentParamNames = Objects.requireNonNullElse(theAdditionalCompartmentParamNames, Set.of()); 989 990 RuntimeResourceDefinition sourceDef = myContext.getResourceDefinition(theSource); 991 List<RuntimeSearchParam> params = 992 new ArrayList<>(sourceDef.getSearchParamsForCompartmentName(theCompartmentName)); 993 994 theAdditionalCompartmentParamNames.stream() 995 .map(sourceDef::getSearchParam) 996 .filter(Objects::nonNull) 997 .forEach(params::add); 998 999 return params.stream() 1000 .flatMap(nextParam -> nextParam.getPathsSplit().stream()) 1001 .filter(StringUtils::isNotBlank) 1002 .flatMap(nextPath -> { 1003 1004 /* 1005 * DSTU3 and before just defined compartments as being (e.g.) named 1006 * Patient with a path like CarePlan.subject 1007 * 1008 * R4 uses a fancier format like CarePlan.subject.where(resolve() is Patient) 1009 * 1010 * The following Regex is a hack to make that efficient at runtime. 1011 */ 1012 String wantType; 1013 Matcher matcher = COMPARTMENT_MATCHER_PATH.matcher(nextPath); 1014 if (matcher.matches()) { 1015 nextPath = matcher.group(1); 1016 wantType = matcher.group(2); 1017 } else { 1018 wantType = null; 1019 } 1020 1021 return getValues(theSource, nextPath, IBaseReference.class).stream() 1022 .filter(nextValue -> isEmpty(wantType) || wantType.equals(getTypeFromReference(nextValue))); 1023 }); 1024 } 1025 1026 private String getTypeFromReference(IBaseReference theReference) { 1027 1028 IIdType nextTargetId = theReference.getReferenceElement().toUnqualifiedVersionless(); 1029 1030 if (isNotBlank(nextTargetId.getResourceType())) { 1031 return nextTargetId.getResourceType(); 1032 } else if (theReference.getResource() != null) { 1033 /* 1034 * If the reference isn't an explicit resource ID, but instead is just 1035 * a resource object, we'll use that type. 1036 */ 1037 return myContext.getResourceType(theReference.getResource()); 1038 } else { 1039 return "No type on reference"; 1040 } 1041 } 1042 1043 private void visitCompartmentOwnersForResource( 1044 String theCompartmentName, 1045 IBaseResource theSource, 1046 @Nullable Set<String> theAdditionalCompartmentParamNames, 1047 ICompartmentOwnerVisitor theConsumer) { 1048 1049 getCompartmentReferencesForResource(theCompartmentName, theSource, theAdditionalCompartmentParamNames) 1050 .flatMap(nextValue -> { 1051 IIdType nextTargetId = nextValue.getReferenceElement().toUnqualifiedVersionless(); 1052 1053 /* 1054 * If the reference isn't an explicit resource ID, but instead is just 1055 * a resource object, we'll calculate its ID and treat the target 1056 * as that. 1057 */ 1058 if (isBlank(nextTargetId.getValue()) && nextValue.getResource() != null) { 1059 IBaseResource nextTarget = nextValue.getResource(); 1060 nextTargetId = nextTarget.getIdElement().toUnqualifiedVersionless(); 1061 if (!nextTargetId.hasResourceType()) { 1062 String resourceType = myContext.getResourceType(nextTarget); 1063 nextTargetId.setParts(null, resourceType, nextTargetId.getIdPart(), null); 1064 } 1065 } 1066 if (isNotBlank(nextTargetId.getValue())) { 1067 return Stream.of(nextTargetId); 1068 } else { 1069 return Stream.empty(); 1070 } 1071 }) 1072 .takeWhile(theConsumer::consume) 1073 .forEach(nop()); 1074 } 1075 1076 private void visit( 1077 IBase theElement, 1078 BaseRuntimeChildDefinition theChildDefinition, 1079 BaseRuntimeElementDefinition<?> theDefinition, 1080 IModelVisitor2 theCallback, 1081 List<IBase> theContainingElementPath, 1082 List<BaseRuntimeChildDefinition> theChildDefinitionPath, 1083 List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) { 1084 if (theChildDefinition != null) { 1085 theChildDefinitionPath.add(theChildDefinition); 1086 } 1087 theContainingElementPath.add(theElement); 1088 theElementDefinitionPath.add(theDefinition); 1089 1090 boolean recurse = theCallback.acceptElement( 1091 theElement, 1092 Collections.unmodifiableList(theContainingElementPath), 1093 Collections.unmodifiableList(theChildDefinitionPath), 1094 Collections.unmodifiableList(theElementDefinitionPath)); 1095 if (recurse) { 1096 1097 /* 1098 * Visit undeclared extensions 1099 */ 1100 if (theElement instanceof ISupportsUndeclaredExtensions) { 1101 ISupportsUndeclaredExtensions containingElement = (ISupportsUndeclaredExtensions) theElement; 1102 for (ExtensionDt nextExt : containingElement.getUndeclaredExtensions()) { 1103 theContainingElementPath.add(nextExt); 1104 theCallback.acceptUndeclaredExtension( 1105 nextExt, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath); 1106 theContainingElementPath.remove(theContainingElementPath.size() - 1); 1107 } 1108 } 1109 1110 /* 1111 * Now visit the children of the given element 1112 */ 1113 switch (theDefinition.getChildType()) { 1114 case ID_DATATYPE: 1115 case PRIMITIVE_XHTML_HL7ORG: 1116 case PRIMITIVE_XHTML: 1117 case PRIMITIVE_DATATYPE: 1118 // These are primitive types, so we don't need to visit their children 1119 break; 1120 case RESOURCE: 1121 case RESOURCE_BLOCK: 1122 case COMPOSITE_DATATYPE: { 1123 BaseRuntimeElementCompositeDefinition<?> childDef = 1124 (BaseRuntimeElementCompositeDefinition<?>) theDefinition; 1125 for (BaseRuntimeChildDefinition nextChild : childDef.getChildrenAndExtension()) { 1126 List<? extends IBase> values = nextChild.getAccessor().getValues(theElement); 1127 if (values != null) { 1128 for (IBase nextValue : values) { 1129 if (nextValue == null) { 1130 continue; 1131 } 1132 if (nextValue.isEmpty()) { 1133 continue; 1134 } 1135 BaseRuntimeElementDefinition<?> childElementDef; 1136 Class<? extends IBase> valueType = nextValue.getClass(); 1137 childElementDef = nextChild.getChildElementDefinitionByDatatype(valueType); 1138 while (childElementDef == null && IBase.class.isAssignableFrom(valueType)) { 1139 childElementDef = nextChild.getChildElementDefinitionByDatatype(valueType); 1140 valueType = (Class<? extends IBase>) valueType.getSuperclass(); 1141 } 1142 1143 Class<? extends IBase> typeClass = nextValue.getClass(); 1144 while (childElementDef == null && IBase.class.isAssignableFrom(typeClass)) { 1145 //noinspection unchecked 1146 typeClass = (Class<? extends IBase>) typeClass.getSuperclass(); 1147 childElementDef = nextChild.getChildElementDefinitionByDatatype(typeClass); 1148 } 1149 1150 Validate.notNull( 1151 childElementDef, 1152 "Found value of type[%s] which is not valid for field[%s] in %s", 1153 nextValue.getClass(), 1154 nextChild.getElementName(), 1155 childDef.getName()); 1156 1157 visit( 1158 nextValue, 1159 nextChild, 1160 childElementDef, 1161 theCallback, 1162 theContainingElementPath, 1163 theChildDefinitionPath, 1164 theElementDefinitionPath); 1165 } 1166 } 1167 } 1168 break; 1169 } 1170 case CONTAINED_RESOURCES: { 1171 BaseContainedDt value = (BaseContainedDt) theElement; 1172 for (IResource next : value.getContainedResources()) { 1173 BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(next); 1174 visit( 1175 next, 1176 null, 1177 def, 1178 theCallback, 1179 theContainingElementPath, 1180 theChildDefinitionPath, 1181 theElementDefinitionPath); 1182 } 1183 break; 1184 } 1185 case EXTENSION_DECLARED: 1186 case UNDECL_EXT: { 1187 throw new IllegalStateException( 1188 Msg.code(1793) + "state should not happen: " + theDefinition.getChildType()); 1189 } 1190 case CONTAINED_RESOURCE_LIST: { 1191 if (theElement != null) { 1192 BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(theElement.getClass()); 1193 visit( 1194 theElement, 1195 null, 1196 def, 1197 theCallback, 1198 theContainingElementPath, 1199 theChildDefinitionPath, 1200 theElementDefinitionPath); 1201 } 1202 break; 1203 } 1204 } 1205 } 1206 1207 if (theChildDefinition != null) { 1208 theChildDefinitionPath.remove(theChildDefinitionPath.size() - 1); 1209 } 1210 theContainingElementPath.remove(theContainingElementPath.size() - 1); 1211 theElementDefinitionPath.remove(theElementDefinitionPath.size() - 1); 1212 } 1213 1214 /** 1215 * Visit all elements in a given resource 1216 * 1217 * <p> 1218 * 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. 1219 * {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource) 1220 * </p> 1221 * 1222 * @param theResource The resource to visit 1223 * @param theVisitor The visitor 1224 */ 1225 public void visit(IBaseResource theResource, IModelVisitor theVisitor) { 1226 BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource); 1227 visit(newMap(), theResource, theResource, null, null, def, theVisitor); 1228 } 1229 1230 public Map<Object, Object> newMap() { 1231 return new IdentityHashMap<>(); 1232 } 1233 1234 /** 1235 * Visit all elements in a given resource or element 1236 * <p> 1237 * <b>THIS ALTERNATE METHOD IS STILL EXPERIMENTAL! USE WITH CAUTION</b> 1238 * </p> 1239 * <p> 1240 * 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. 1241 * {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource) 1242 * </p> 1243 * 1244 * @param theElement The element to visit 1245 * @param theVisitor The visitor 1246 */ 1247 public void visit(IBase theElement, IModelVisitor2 theVisitor) { 1248 BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(theElement.getClass()); 1249 if (def instanceof BaseRuntimeElementCompositeDefinition) { 1250 visit(theElement, null, def, theVisitor, new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); 1251 } else if (theElement instanceof IBaseExtension) { 1252 theVisitor.acceptUndeclaredExtension( 1253 (IBaseExtension<?, ?>) theElement, 1254 Collections.emptyList(), 1255 Collections.emptyList(), 1256 Collections.emptyList()); 1257 } else { 1258 theVisitor.acceptElement( 1259 theElement, Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); 1260 } 1261 } 1262 1263 private void visit( 1264 Map<Object, Object> theStack, 1265 IBaseResource theResource, 1266 IBase theElement, 1267 List<String> thePathToElement, 1268 BaseRuntimeChildDefinition theChildDefinition, 1269 BaseRuntimeElementDefinition<?> theDefinition, 1270 IModelVisitor theCallback) { 1271 List<String> pathToElement = addNameToList(thePathToElement, theChildDefinition); 1272 1273 if (theStack.put(theElement, theElement) != null) { 1274 return; 1275 } 1276 1277 theCallback.acceptElement(theResource, theElement, pathToElement, theChildDefinition, theDefinition); 1278 1279 BaseRuntimeElementDefinition<?> def = theDefinition; 1280 if (def.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST) { 1281 Class<? extends IBase> clazz = theElement.getClass(); 1282 def = myContext.getElementDefinition(clazz); 1283 Validate.notNull(def, "Unable to find element definition for class: %s", clazz); 1284 } 1285 1286 if (theElement instanceof IBaseReference) { 1287 IBaseResource target = ((IBaseReference) theElement).getResource(); 1288 if (target != null) { 1289 if (target.getIdElement().hasIdPart() == false 1290 || target.getIdElement().isLocal()) { 1291 RuntimeResourceDefinition targetDef = myContext.getResourceDefinition(target); 1292 visit(theStack, target, target, pathToElement, null, targetDef, theCallback); 1293 } 1294 } 1295 } 1296 1297 switch (def.getChildType()) { 1298 case ID_DATATYPE: 1299 case PRIMITIVE_XHTML_HL7ORG: 1300 case PRIMITIVE_XHTML: 1301 case PRIMITIVE_DATATYPE: 1302 // These are primitive types 1303 break; 1304 case RESOURCE: 1305 case RESOURCE_BLOCK: 1306 case COMPOSITE_DATATYPE: { 1307 BaseRuntimeElementCompositeDefinition<?> childDef = (BaseRuntimeElementCompositeDefinition<?>) def; 1308 List<BaseRuntimeChildDefinition> childrenAndExtensionDefs = childDef.getChildrenAndExtension(); 1309 for (BaseRuntimeChildDefinition nextChild : childrenAndExtensionDefs) { 1310 1311 List<?> values = nextChild.getAccessor().getValues(theElement); 1312 1313 if (values != null) { 1314 for (Object nextValueObject : values) { 1315 IBase nextValue; 1316 try { 1317 nextValue = (IBase) nextValueObject; 1318 } catch (ClassCastException e) { 1319 String s = "Found instance of " + nextValueObject.getClass() 1320 + " - Did you set a field value to the incorrect type? Expected " 1321 + IBase.class.getName(); 1322 throw new ClassCastException(Msg.code(1794) + s); 1323 } 1324 if (nextValue == null) { 1325 continue; 1326 } 1327 if (nextValue.isEmpty()) { 1328 continue; 1329 } 1330 BaseRuntimeElementDefinition<?> childElementDef; 1331 Class<? extends IBase> clazz = nextValue.getClass(); 1332 childElementDef = nextChild.getChildElementDefinitionByDatatype(clazz); 1333 1334 if (childElementDef == null) { 1335 childElementDef = myContext.getElementDefinition(clazz); 1336 Validate.notNull( 1337 childElementDef, "Unable to find element definition for class: %s", clazz); 1338 } 1339 1340 if (nextChild instanceof RuntimeChildDirectResource) { 1341 // Don't descend into embedded resources 1342 theCallback.acceptElement(theResource, nextValue, null, nextChild, childElementDef); 1343 } else { 1344 visit( 1345 theStack, 1346 theResource, 1347 nextValue, 1348 pathToElement, 1349 nextChild, 1350 childElementDef, 1351 theCallback); 1352 } 1353 } 1354 } 1355 } 1356 break; 1357 } 1358 case CONTAINED_RESOURCES: { 1359 BaseContainedDt value = (BaseContainedDt) theElement; 1360 for (IResource next : value.getContainedResources()) { 1361 def = myContext.getResourceDefinition(next); 1362 visit(theStack, next, next, pathToElement, null, def, theCallback); 1363 } 1364 break; 1365 } 1366 case CONTAINED_RESOURCE_LIST: 1367 case EXTENSION_DECLARED: 1368 case UNDECL_EXT: { 1369 throw new IllegalStateException(Msg.code(1795) + "state should not happen: " + def.getChildType()); 1370 } 1371 } 1372 1373 theStack.remove(theElement); 1374 } 1375 1376 /** 1377 * Returns all embedded resources that are found embedded within <code>theResource</code>. 1378 * An embedded resource is a resource that can be found as a direct child within a resource, 1379 * as opposed to being referenced by the resource. 1380 * <p> 1381 * Examples include resources found within <code>Bundle.entry.resource</code> 1382 * and <code>Parameters.parameter.resource</code>, as well as contained resources 1383 * found within <code>Resource.contained</code> 1384 * </p> 1385 * 1386 * @param theRecurse Should embedded resources be recursively scanned for further embedded 1387 * resources 1388 * @return A collection containing the embedded resources. Order is arbitrary. 1389 */ 1390 public Collection<IBaseResource> getAllEmbeddedResources(IBaseResource theResource, boolean theRecurse) { 1391 Validate.notNull(theResource, "theResource must not be null"); 1392 ArrayList<IBaseResource> retVal = new ArrayList<>(); 1393 1394 visit(theResource, new IModelVisitor2() { 1395 @Override 1396 public boolean acceptElement( 1397 IBase theElement, 1398 List<IBase> theContainingElementPath, 1399 List<BaseRuntimeChildDefinition> theChildDefinitionPath, 1400 List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) { 1401 if (theElement == theResource) { 1402 return true; 1403 } 1404 if (theElement instanceof IBaseResource) { 1405 retVal.add((IBaseResource) theElement); 1406 return theRecurse; 1407 } 1408 return true; 1409 } 1410 1411 @Override 1412 public boolean acceptUndeclaredExtension( 1413 IBaseExtension<?, ?> theNextExt, 1414 List<IBase> theContainingElementPath, 1415 List<BaseRuntimeChildDefinition> theChildDefinitionPath, 1416 List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) { 1417 return true; 1418 } 1419 }); 1420 1421 return retVal; 1422 } 1423 1424 /** 1425 * Clear all content on a resource 1426 */ 1427 public void clear(IBaseResource theInput) { 1428 visit(theInput, new IModelVisitor2() { 1429 @Override 1430 public boolean acceptElement( 1431 IBase theElement, 1432 List<IBase> theContainingElementPath, 1433 List<BaseRuntimeChildDefinition> theChildDefinitionPath, 1434 List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) { 1435 if (theElement instanceof IPrimitiveType) { 1436 ((IPrimitiveType) theElement).setValueAsString(null); 1437 } 1438 return true; 1439 } 1440 1441 @Override 1442 public boolean acceptUndeclaredExtension( 1443 IBaseExtension<?, ?> theNextExt, 1444 List<IBase> theContainingElementPath, 1445 List<BaseRuntimeChildDefinition> theChildDefinitionPath, 1446 List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) { 1447 theNextExt.setUrl(null); 1448 theNextExt.setValue(null); 1449 return true; 1450 } 1451 }); 1452 } 1453 1454 private void containResourcesForEncoding(ContainedResources theContained, IBaseResource theResource) { 1455 List<IBaseReference> allReferences = getAllPopulatedChildElementsOfType(theResource, IBaseReference.class); 1456 1457 // Note that we process all contained resources that have arrived here with an ID contained resources first, so 1458 // that we don't accidentally auto-assign an ID 1459 // which may collide with a resource we have yet to process. 1460 // See: https://github.com/hapifhir/hapi-fhir/issues/6403 1461 allReferences.sort(REFERENCES_WITH_IDS_FIRST); 1462 1463 for (IBaseReference next : allReferences) { 1464 IBaseResource resource = next.getResource(); 1465 if (resource == null && next.getReferenceElement().isLocal()) { 1466 if (theContained.hasExistingIdToContainedResource()) { 1467 IBaseResource potentialTarget = theContained 1468 .getExistingIdToContainedResource() 1469 .remove(next.getReferenceElement().getValue()); 1470 if (potentialTarget != null) { 1471 theContained.addContained(next.getReferenceElement(), potentialTarget); 1472 containResourcesForEncoding(theContained, potentialTarget); 1473 } 1474 } 1475 } 1476 } 1477 1478 for (IBaseReference next : allReferences) { 1479 IBaseResource resource = next.getResource(); 1480 if (resource != null) { 1481 if (resource.getIdElement().isEmpty() || resource.getIdElement().isLocal()) { 1482 1483 IIdType id = theContained.addContained(resource); 1484 if (id == null) { 1485 continue; 1486 } 1487 getContainedResourceList(theResource).add(resource); 1488 1489 String idString = id.getValue(); 1490 if (!idString.startsWith("#")) { 1491 idString = "#" + idString; 1492 } 1493 1494 next.setReference(idString); 1495 next.setResource(null); 1496 if (resource.getIdElement().isLocal() && theContained.hasExistingIdToContainedResource()) { 1497 theContained 1498 .getExistingIdToContainedResource() 1499 .remove(resource.getIdElement().getValue()); 1500 } 1501 } else { 1502 IIdType previouslyContainedResourceId = theContained.getPreviouslyContainedResourceId(resource); 1503 if (previouslyContainedResourceId != null) { 1504 if (theContained.getResourceId(resource) == null) { 1505 theContained.addContained(previouslyContainedResourceId, resource); 1506 getContainedResourceList(theResource).add(resource); 1507 } 1508 } 1509 } 1510 } 1511 } 1512 } 1513 1514 /** 1515 * Iterate through the whole resource and identify any contained resources. Optionally this method 1516 * can also assign IDs and modify references where the resource link has been specified but not the 1517 * reference text. 1518 * 1519 * @since 5.4.0 1520 */ 1521 public ContainedResources containResources( 1522 IBaseResource theResource, ContainedResources theParentContainedResources, boolean theStoreResults) { 1523 1524 if (theStoreResults) { 1525 Object cachedValue = theResource.getUserData(USER_DATA_KEY_CONTAIN_RESOURCES_COMPLETED); 1526 if (cachedValue != null) { 1527 return (ContainedResources) cachedValue; 1528 } 1529 } 1530 1531 ContainedResources contained = new ContainedResources(); 1532 1533 List<? extends IBaseResource> containedResources = getContainedResourceList(theResource); 1534 for (IBaseResource next : containedResources) { 1535 String nextId = next.getIdElement().getValue(); 1536 if (StringUtils.isNotBlank(nextId)) { 1537 while (nextId.startsWith("#")) { 1538 ourLog.warn( 1539 "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.", 1540 nextId); 1541 nextId = nextId.substring(1); 1542 } 1543 next.getIdElement().setValue(nextId); 1544 } 1545 contained.addContained(next); 1546 } 1547 1548 if (myContext.getParserOptions().isAutoContainReferenceTargetsWithNoId()) { 1549 if (theParentContainedResources != null) { 1550 if (theParentContainedResources.hasResourceToIdValues()) { 1551 contained 1552 .getPreviouslyContainedResourceToIdMap() 1553 .putAll(theParentContainedResources.getResourceToIdMap()); 1554 } 1555 if (theParentContainedResources.hasPreviouslyContainedResourceToIdValues()) { 1556 contained 1557 .getPreviouslyContainedResourceToIdMap() 1558 .putAll(theParentContainedResources.getPreviouslyContainedResourceToIdMap()); 1559 } 1560 } 1561 1562 containResourcesForEncoding(contained, theResource); 1563 } 1564 1565 if (theStoreResults) { 1566 theResource.setUserData(USER_DATA_KEY_CONTAIN_RESOURCES_COMPLETED, contained); 1567 } 1568 1569 return contained; 1570 } 1571 1572 @SuppressWarnings("unchecked") 1573 private <T extends IBaseResource> List<T> getContainedResourceList(T theResource) { 1574 List<T> containedResources = Collections.emptyList(); 1575 if (theResource instanceof IResource) { 1576 containedResources = 1577 (List<T>) ((IResource) theResource).getContained().getContainedResources(); 1578 } else if (theResource instanceof IDomainResource) { 1579 containedResources = (List<T>) ((IDomainResource) theResource).getContained(); 1580 } 1581 return containedResources; 1582 } 1583 1584 /** 1585 * Adds and returns a new element at the given path within the given structure. The paths used here 1586 * are <b>not FHIRPath expressions</b> but instead just simple dot-separated path expressions. 1587 * <p> 1588 * Only the last entry in the path is always created, existing repetitions of elements before 1589 * the final dot are returned if they exists (although they are created if they do not). For example, 1590 * given the path <code>Patient.name.given</code>, a new repetition of <code>given</code> is always 1591 * added to the first (index 0) repetition of the name. If an index-0 repetition of <code>name</code> 1592 * already exists, it is added to. If one does not exist, it if created and then added to. 1593 * </p> 1594 * <p> 1595 * If the last element in the path refers to a non-repeatable element that is already present and 1596 * is not empty, a {@link DataFormatException} error will be thrown. 1597 * </p> 1598 * 1599 * @param theTarget The element to add to. This will often be a {@link IBaseResource resource} 1600 * instance, but does not need to be. 1601 * @param thePath The path. 1602 * @return The newly added element 1603 * @throws DataFormatException If the path is invalid or does not end with either a repeatable element, or 1604 * an element that is non-repeatable but not already populated. 1605 */ 1606 @SuppressWarnings("unchecked") 1607 @Nonnull 1608 public <T extends IBase> T addElement(@Nonnull IBase theTarget, @Nonnull String thePath) { 1609 return (T) doAddElement(theTarget, thePath, 1).get(0); 1610 } 1611 1612 @SuppressWarnings("unchecked") 1613 private <T extends IBase> List<T> doAddElement(IBase theTarget, String thePath, int theElementsToAdd) { 1614 if (theElementsToAdd == 0) { 1615 return Collections.emptyList(); 1616 } 1617 1618 IBase target = theTarget; 1619 BaseRuntimeElementCompositeDefinition<?> def = 1620 (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(target.getClass()); 1621 List<String> parts = parsePath(def, thePath); 1622 1623 for (int i = 0, partsSize = parts.size(); ; i++) { 1624 String nextPart = parts.get(i); 1625 boolean lastPart = i == partsSize - 1; 1626 1627 BaseRuntimeChildDefinition nextChild = def.getChildByName(nextPart); 1628 if (nextChild == null) { 1629 throw new DataFormatException(Msg.code(1796) + "Invalid path " + thePath + ": Element of type " 1630 + def.getName() + " has no child named " + nextPart + ". Valid names: " 1631 + def.getChildrenAndExtension().stream() 1632 .map(BaseRuntimeChildDefinition::getElementName) 1633 .sorted() 1634 .collect(Collectors.joining(", "))); 1635 } 1636 1637 List<IBase> childValues = nextChild.getAccessor().getValues(target); 1638 IBase childValue; 1639 if (childValues.size() > 0 && !lastPart) { 1640 childValue = childValues.get(0); 1641 } else { 1642 1643 if (lastPart) { 1644 if (!childValues.isEmpty()) { 1645 if (theElementsToAdd == -1) { 1646 return (List<T>) Collections.singletonList(childValues.get(0)); 1647 } else if (nextChild.getMax() == 1 1648 && !childValues.get(0).isEmpty()) { 1649 throw new DataFormatException( 1650 Msg.code(1797) + "Element at path " + thePath + " is not repeatable and not empty"); 1651 } else if (nextChild.getMax() == 1 && childValues.get(0).isEmpty()) { 1652 return (List<T>) Collections.singletonList(childValues.get(0)); 1653 } 1654 } 1655 } 1656 1657 BaseRuntimeElementDefinition<?> elementDef = nextChild.getChildByName(nextPart); 1658 childValue = elementDef.newInstance(nextChild.getInstanceConstructorArguments()); 1659 nextChild.getMutator().addValue(target, childValue); 1660 1661 if (lastPart) { 1662 if (theElementsToAdd == 1 || theElementsToAdd == -1) { 1663 return (List<T>) Collections.singletonList(childValue); 1664 } else { 1665 if (nextChild.getMax() == 1) { 1666 throw new DataFormatException(Msg.code(1798) + "Can not add multiple values at path " 1667 + thePath + ": Element does not repeat"); 1668 } 1669 1670 List<T> values = (List<T>) Lists.newArrayList(childValue); 1671 for (int j = 1; j < theElementsToAdd; j++) { 1672 childValue = elementDef.newInstance(nextChild.getInstanceConstructorArguments()); 1673 nextChild.getMutator().addValue(target, childValue); 1674 values.add((T) childValue); 1675 } 1676 1677 return values; 1678 } 1679 } 1680 } 1681 1682 target = childValue; 1683 1684 if (!lastPart) { 1685 BaseRuntimeElementDefinition<?> nextDef = myContext.getElementDefinition(target.getClass()); 1686 if (!(nextDef instanceof BaseRuntimeElementCompositeDefinition)) { 1687 throw new DataFormatException(Msg.code(1799) + "Invalid path " + thePath + ": Element of type " 1688 + def.getName() + " has no child named " + nextPart + " (this is a primitive type)"); 1689 } 1690 def = (BaseRuntimeElementCompositeDefinition<?>) nextDef; 1691 } 1692 } 1693 } 1694 1695 /** 1696 * Adds and returns a new element at the given path within the given structure. The paths used here 1697 * are <b>not FHIRPath expressions</b> but instead just simple dot-separated path expressions. 1698 * <p> 1699 * This method follows all of the same semantics as {@link #addElement(IBase, String)} but it 1700 * requires the path to point to an element with a primitive datatype and set the value of 1701 * the datatype to the given value. 1702 * </p> 1703 * 1704 * @param theTarget The element to add to. This will often be a {@link IBaseResource resource} 1705 * instance, but does not need to be. 1706 * @param thePath The path. 1707 * @param theValue The value to set, or <code>null</code>. 1708 * @return The newly added element 1709 * @throws DataFormatException If the path is invalid or does not end with either a repeatable element, or 1710 * an element that is non-repeatable but not already populated. 1711 */ 1712 @SuppressWarnings("unchecked") 1713 @Nonnull 1714 public <T extends IBase> T addElement( 1715 @Nonnull IBase theTarget, @Nonnull String thePath, @Nullable String theValue) { 1716 T value = (T) doAddElement(theTarget, thePath, 1).get(0); 1717 if (!(value instanceof IPrimitiveType)) { 1718 throw new DataFormatException( 1719 Msg.code(1800) + "Element at path " + thePath + " is not a primitive datatype. Found: " 1720 + myContext.getElementDefinition(value.getClass()).getName()); 1721 } 1722 1723 ((IPrimitiveType<?>) value).setValueAsString(theValue); 1724 1725 return value; 1726 } 1727 1728 /** 1729 * Adds and returns a new element at the given path within the given structure. The paths used here 1730 * are <b>not FHIRPath expressions</b> but instead just simple dot-separated path expressions. 1731 * <p> 1732 * This method follows all of the same semantics as {@link #addElement(IBase, String)} but it 1733 * requires the path to point to an element with a primitive datatype and set the value of 1734 * the datatype to the given value. 1735 * </p> 1736 * 1737 * @param theTarget The element to add to. This will often be a {@link IBaseResource resource} 1738 * instance, but does not need to be. 1739 * @param thePath The path. 1740 * @param theValue The value to set, or <code>null</code>. 1741 * @return The newly added element 1742 * @throws DataFormatException If the path is invalid or does not end with either a repeatable element, or 1743 * an element that is non-repeatable but not already populated. 1744 */ 1745 @SuppressWarnings("unchecked") 1746 @Nonnull 1747 public <T extends IBase> T setElement( 1748 @Nonnull IBase theTarget, @Nonnull String thePath, @Nullable String theValue) { 1749 T value = (T) doAddElement(theTarget, thePath, -1).get(0); 1750 if (!(value instanceof IPrimitiveType)) { 1751 throw new DataFormatException( 1752 Msg.code(1801) + "Element at path " + thePath + " is not a primitive datatype. Found: " 1753 + myContext.getElementDefinition(value.getClass()).getName()); 1754 } 1755 1756 ((IPrimitiveType<?>) value).setValueAsString(theValue); 1757 1758 return value; 1759 } 1760 1761 /** 1762 * This method has the same semantics as {@link #addElement(IBase, String, String)} but adds 1763 * a collection of primitives instead of a single one. 1764 * 1765 * @param theTarget The element to add to. This will often be a {@link IBaseResource resource} 1766 * instance, but does not need to be. 1767 * @param thePath The path. 1768 * @param theValues The values to set, or <code>null</code>. 1769 */ 1770 public void addElements(IBase theTarget, String thePath, Collection<String> theValues) { 1771 List<IBase> targets = doAddElement(theTarget, thePath, theValues.size()); 1772 Iterator<String> valuesIter = theValues.iterator(); 1773 for (IBase target : targets) { 1774 1775 if (!(target instanceof IPrimitiveType)) { 1776 throw new DataFormatException(Msg.code(1802) + "Element at path " + thePath 1777 + " is not a primitive datatype. Found: " 1778 + myContext.getElementDefinition(target.getClass()).getName()); 1779 } 1780 1781 ((IPrimitiveType<?>) target).setValueAsString(valuesIter.next()); 1782 } 1783 } 1784 1785 /** 1786 * Clones a resource object, copying all data elements from theSource into a new copy of the same type. 1787 * <p> 1788 * Note that: 1789 * <ul> 1790 * <li>Only FHIR data elements are copied (i.e. user data maps are not copied)</li> 1791 * <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> 1792 * </ul> 1793 * 1794 * @param theSource The source resource 1795 * @return A copy of the source resource 1796 * @since 5.6.0 1797 */ 1798 @SuppressWarnings("unchecked") 1799 public <T extends IBaseResource> T clone(T theSource) { 1800 Validate.notNull(theSource, "theSource must not be null"); 1801 T target = (T) myContext.getResourceDefinition(theSource).newInstance(); 1802 cloneInto(theSource, target, false); 1803 return target; 1804 } 1805 1806 @FunctionalInterface 1807 private interface ICompartmentOwnerVisitor { 1808 1809 /** 1810 * @return Returns true if we should keep looking for more 1811 */ 1812 boolean consume(IIdType theCompartmentOwner); 1813 } 1814 1815 public static class ContainedResources { 1816 private List<IBaseResource> myResourceList; 1817 private IdentityHashMap<IBaseResource, IIdType> myResourceToIdMap; 1818 private IdentityHashMap<IBaseResource, IIdType> myPreviouslyContainedResourceToIdMap; 1819 private Map<String, IBaseResource> myExistingIdToContainedResourceMap; 1820 1821 public ContainedResources() { 1822 super(); 1823 } 1824 1825 public Map<String, IBaseResource> getExistingIdToContainedResource() { 1826 if (myExistingIdToContainedResourceMap == null) { 1827 myExistingIdToContainedResourceMap = new HashMap<>(); 1828 } 1829 return myExistingIdToContainedResourceMap; 1830 } 1831 1832 public IIdType addContained(IBaseResource theResource) { 1833 if (this.getResourceId(theResource) != null) { 1834 // Prevent infinite recursion if there are circular loops in the contained resources 1835 return null; 1836 } 1837 1838 IIdType existing = getResourceToIdMap().get(theResource); 1839 if (existing != null) { 1840 return existing; 1841 } 1842 1843 IIdType newId = theResource.getIdElement(); 1844 if (isBlank(newId.getValue())) { 1845 newId.setValue(UUID.randomUUID().toString()); 1846 } 1847 1848 getResourceToIdMap().put(theResource, newId); 1849 getOrCreateResourceList().add(theResource); 1850 return newId; 1851 } 1852 1853 public void addContained(IIdType theId, IBaseResource theResource) { 1854 if (!getResourceToIdMap().containsKey(theResource)) { 1855 getResourceToIdMap().put(theResource, theId); 1856 getOrCreateResourceList().add(theResource); 1857 } 1858 } 1859 1860 public List<IBaseResource> getContainedResources() { 1861 if (getResourceToIdMap() == null) { 1862 return Collections.emptyList(); 1863 } 1864 return getOrCreateResourceList(); 1865 } 1866 1867 public IIdType getResourceId(IBaseResource theNext) { 1868 if (getResourceToIdMap() == null) { 1869 return null; 1870 } 1871 1872 var idFromMap = getResourceToIdMap().get(theNext); 1873 if (idFromMap != null) { 1874 return idFromMap; 1875 } else if (theNext.getIdElement().getIdPart() != null) { 1876 return getResourceToIdMap().values().stream() 1877 .filter(id -> theNext.getIdElement().getIdPart().equals(id.getIdPart())) 1878 .findAny() 1879 .orElse(null); 1880 } else { 1881 return null; 1882 } 1883 } 1884 1885 private List<IBaseResource> getOrCreateResourceList() { 1886 if (myResourceList == null) { 1887 myResourceList = new ArrayList<>(); 1888 } 1889 return myResourceList; 1890 } 1891 1892 private boolean hasResourceToIdValues() { 1893 return myResourceToIdMap != null && !myResourceToIdMap.isEmpty(); 1894 } 1895 1896 private IdentityHashMap<IBaseResource, IIdType> getResourceToIdMap() { 1897 if (myResourceToIdMap == null) { 1898 myResourceToIdMap = new IdentityHashMap<>(); 1899 } 1900 return myResourceToIdMap; 1901 } 1902 1903 public boolean isEmpty() { 1904 if (myResourceToIdMap == null) { 1905 return true; 1906 } 1907 return myResourceToIdMap.isEmpty(); 1908 } 1909 1910 public boolean hasExistingIdToContainedResource() { 1911 return myExistingIdToContainedResourceMap != null; 1912 } 1913 1914 public IdentityHashMap<IBaseResource, IIdType> getPreviouslyContainedResourceToIdMap() { 1915 if (myPreviouslyContainedResourceToIdMap == null) { 1916 myPreviouslyContainedResourceToIdMap = new IdentityHashMap<>(); 1917 } 1918 return myPreviouslyContainedResourceToIdMap; 1919 } 1920 1921 public boolean hasPreviouslyContainedResourceToIdValues() { 1922 return myPreviouslyContainedResourceToIdMap != null && !myPreviouslyContainedResourceToIdMap.isEmpty(); 1923 } 1924 1925 public IIdType getPreviouslyContainedResourceId(IBaseResource theResource) { 1926 if (hasPreviouslyContainedResourceToIdValues()) { 1927 return getPreviouslyContainedResourceToIdMap().get(theResource); 1928 } 1929 return null; 1930 } 1931 } 1932}