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