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