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