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