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 params.addAll(additionalParams); 760 } 761 } 762 763 764 for (RuntimeSearchParam nextParam : params) { 765 for (String nextPath : nextParam.getPathsSplit()) { 766 767 /* 768 * DSTU3 and before just defined compartments as being (e.g.) named 769 * Patient with a path like CarePlan.subject 770 * 771 * R4 uses a fancier format like CarePlan.subject.where(resolve() is Patient) 772 * 773 * The following Regex is a hack to make that efficient at runtime. 774 */ 775 String wantType = null; 776 Pattern pattern = COMPARTMENT_MATCHER_PATH; 777 Matcher matcher = pattern.matcher(nextPath); 778 if (matcher.matches()) { 779 nextPath = matcher.group(1); 780 wantType = matcher.group(2); 781 } 782 783 List<IBaseReference> values = getValues(theSource, nextPath, IBaseReference.class); 784 for (IBaseReference nextValue : values) { 785 IIdType nextTargetId = nextValue.getReferenceElement(); 786 String nextRef = nextTargetId.toUnqualifiedVersionless().getValue(); 787 788 /* 789 * If the reference isn't an explicit resource ID, but instead is just 790 * a resource object, we'll calculate its ID and treat the target 791 * as that. 792 */ 793 if (isBlank(nextRef) && nextValue.getResource() != null) { 794 IBaseResource nextTarget = nextValue.getResource(); 795 nextTargetId = nextTarget.getIdElement().toUnqualifiedVersionless(); 796 if (!nextTargetId.hasResourceType()) { 797 String resourceType = myContext.getResourceType(nextTarget); 798 nextTargetId.setParts(null, resourceType, nextTargetId.getIdPart(), null); 799 } 800 nextRef = nextTargetId.getValue(); 801 } 802 803 if (isNotBlank(wantType)) { 804 String nextTargetIdResourceType = nextTargetId.getResourceType(); 805 if (nextTargetIdResourceType == null || !nextTargetIdResourceType.equals(wantType)) { 806 continue; 807 } 808 } 809 810 if (wantRef.equals(nextRef)) { 811 return true; 812 } 813 } 814 } 815 } 816 817 return false; 818 } 819 820 private void visit(IBase theElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition<?> theDefinition, IModelVisitor2 theCallback, List<IBase> theContainingElementPath, 821 List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) { 822 if (theChildDefinition != null) { 823 theChildDefinitionPath.add(theChildDefinition); 824 } 825 theContainingElementPath.add(theElement); 826 theElementDefinitionPath.add(theDefinition); 827 828 boolean recurse = theCallback.acceptElement(theElement, Collections.unmodifiableList(theContainingElementPath), Collections.unmodifiableList(theChildDefinitionPath), 829 Collections.unmodifiableList(theElementDefinitionPath)); 830 if (recurse) { 831 832 /* 833 * Visit undeclared extensions 834 */ 835 if (theElement instanceof ISupportsUndeclaredExtensions) { 836 ISupportsUndeclaredExtensions containingElement = (ISupportsUndeclaredExtensions) theElement; 837 for (ExtensionDt nextExt : containingElement.getUndeclaredExtensions()) { 838 theContainingElementPath.add(nextExt); 839 theCallback.acceptUndeclaredExtension(nextExt, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath); 840 theContainingElementPath.remove(theContainingElementPath.size() - 1); 841 } 842 } 843 844 /* 845 * Now visit the children of the given element 846 */ 847 switch (theDefinition.getChildType()) { 848 case ID_DATATYPE: 849 case PRIMITIVE_XHTML_HL7ORG: 850 case PRIMITIVE_XHTML: 851 case PRIMITIVE_DATATYPE: 852 // These are primitive types, so we don't need to visit their children 853 break; 854 case RESOURCE: 855 case RESOURCE_BLOCK: 856 case COMPOSITE_DATATYPE: { 857 BaseRuntimeElementCompositeDefinition<?> childDef = (BaseRuntimeElementCompositeDefinition<?>) theDefinition; 858 for (BaseRuntimeChildDefinition nextChild : childDef.getChildrenAndExtension()) { 859 List<? extends IBase> values = nextChild.getAccessor().getValues(theElement); 860 if (values != null) { 861 for (IBase nextValue : values) { 862 if (nextValue == null) { 863 continue; 864 } 865 if (nextValue.isEmpty()) { 866 continue; 867 } 868 BaseRuntimeElementDefinition<?> childElementDef; 869 Class<? extends IBase> valueType = nextValue.getClass(); 870 childElementDef = nextChild.getChildElementDefinitionByDatatype(valueType); 871 while (childElementDef == null && IBase.class.isAssignableFrom(valueType)) { 872 childElementDef = nextChild.getChildElementDefinitionByDatatype(valueType); 873 valueType = (Class<? extends IBase>) valueType.getSuperclass(); 874 } 875 876 Class<? extends IBase> typeClass = nextValue.getClass(); 877 while (childElementDef == null && IBase.class.isAssignableFrom(typeClass)) { 878 //noinspection unchecked 879 typeClass = (Class<? extends IBase>) typeClass.getSuperclass(); 880 childElementDef = nextChild.getChildElementDefinitionByDatatype(typeClass); 881 } 882 883 Validate.notNull(childElementDef, "Found value of type[%s] which is not valid for field[%s] in %s", nextValue.getClass(), nextChild.getElementName(), childDef.getName()); 884 885 visit(nextValue, nextChild, childElementDef, theCallback, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath); 886 } 887 } 888 } 889 break; 890 } 891 case CONTAINED_RESOURCES: { 892 BaseContainedDt value = (BaseContainedDt) theElement; 893 for (IResource next : value.getContainedResources()) { 894 BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(next); 895 visit(next, null, def, theCallback, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath); 896 } 897 break; 898 } 899 case EXTENSION_DECLARED: 900 case UNDECL_EXT: { 901 throw new IllegalStateException(Msg.code(1793) + "state should not happen: " + theDefinition.getChildType()); 902 } 903 case CONTAINED_RESOURCE_LIST: { 904 if (theElement != null) { 905 BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(theElement.getClass()); 906 visit(theElement, null, def, theCallback, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath); 907 } 908 break; 909 } 910 } 911 912 } 913 914 if (theChildDefinition != null) { 915 theChildDefinitionPath.remove(theChildDefinitionPath.size() - 1); 916 } 917 theContainingElementPath.remove(theContainingElementPath.size() - 1); 918 theElementDefinitionPath.remove(theElementDefinitionPath.size() - 1); 919 } 920 921 /** 922 * Visit all elements in a given resource 923 * 924 * <p> 925 * 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. 926 * {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource) 927 * </p> 928 * 929 * @param theResource The resource to visit 930 * @param theVisitor The visitor 931 */ 932 public void visit(IBaseResource theResource, IModelVisitor theVisitor) { 933 BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource); 934 visit(newMap(), theResource, theResource, null, null, def, theVisitor); 935 } 936 937 public Map<Object, Object> newMap() { 938 return new IdentityHashMap<>(); 939 } 940 941 /** 942 * Visit all elements in a given resource or element 943 * <p> 944 * <b>THIS ALTERNATE METHOD IS STILL EXPERIMENTAL! USE WITH CAUTION</b> 945 * </p> 946 * <p> 947 * 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. 948 * {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource) 949 * </p> 950 * 951 * @param theElement The element to visit 952 * @param theVisitor The visitor 953 */ 954 public void visit(IBase theElement, IModelVisitor2 theVisitor) { 955 BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(theElement.getClass()); 956 if (def instanceof BaseRuntimeElementCompositeDefinition) { 957 BaseRuntimeElementCompositeDefinition<?> defComposite = (BaseRuntimeElementCompositeDefinition<?>) def; 958 visit(theElement, null, def, theVisitor, new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); 959 } else if (theElement instanceof IBaseExtension) { 960 theVisitor.acceptUndeclaredExtension((IBaseExtension<?, ?>) theElement, Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); 961 } else { 962 theVisitor.acceptElement(theElement, Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); 963 } 964 } 965 966 private void visit(Map<Object, Object> theStack, IBaseResource theResource, IBase theElement, List<String> thePathToElement, BaseRuntimeChildDefinition theChildDefinition, 967 BaseRuntimeElementDefinition<?> theDefinition, IModelVisitor theCallback) { 968 List<String> pathToElement = addNameToList(thePathToElement, theChildDefinition); 969 970 if (theStack.put(theElement, theElement) != null) { 971 return; 972 } 973 974 theCallback.acceptElement(theResource, theElement, pathToElement, theChildDefinition, theDefinition); 975 976 BaseRuntimeElementDefinition<?> def = theDefinition; 977 if (def.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST) { 978 Class<? extends IBase> clazz = theElement.getClass(); 979 def = myContext.getElementDefinition(clazz); 980 Validate.notNull(def, "Unable to find element definition for class: %s", clazz); 981 } 982 983 if (theElement instanceof IBaseReference) { 984 IBaseResource target = ((IBaseReference) theElement).getResource(); 985 if (target != null) { 986 if (target.getIdElement().hasIdPart() == false || target.getIdElement().isLocal()) { 987 RuntimeResourceDefinition targetDef = myContext.getResourceDefinition(target); 988 visit(theStack, target, target, pathToElement, null, targetDef, theCallback); 989 } 990 } 991 } 992 993 switch (def.getChildType()) { 994 case ID_DATATYPE: 995 case PRIMITIVE_XHTML_HL7ORG: 996 case PRIMITIVE_XHTML: 997 case PRIMITIVE_DATATYPE: 998 // These are primitive types 999 break; 1000 case RESOURCE: 1001 case RESOURCE_BLOCK: 1002 case COMPOSITE_DATATYPE: { 1003 BaseRuntimeElementCompositeDefinition<?> childDef = (BaseRuntimeElementCompositeDefinition<?>) def; 1004 List<BaseRuntimeChildDefinition> childrenAndExtensionDefs = childDef.getChildrenAndExtension(); 1005 for (BaseRuntimeChildDefinition nextChild : childrenAndExtensionDefs) { 1006 1007 List<?> values = nextChild.getAccessor().getValues(theElement); 1008 1009 if (values != null) { 1010 for (Object nextValueObject : values) { 1011 IBase nextValue; 1012 try { 1013 nextValue = (IBase) nextValueObject; 1014 } catch (ClassCastException e) { 1015 String s = "Found instance of " + nextValueObject.getClass() + " - Did you set a field value to the incorrect type? Expected " + IBase.class.getName(); 1016 throw new ClassCastException(Msg.code(1794) + s); 1017 } 1018 if (nextValue == null) { 1019 continue; 1020 } 1021 if (nextValue.isEmpty()) { 1022 continue; 1023 } 1024 BaseRuntimeElementDefinition<?> childElementDef; 1025 Class<? extends IBase> clazz = nextValue.getClass(); 1026 childElementDef = nextChild.getChildElementDefinitionByDatatype(clazz); 1027 1028 if (childElementDef == null) { 1029 childElementDef = myContext.getElementDefinition(clazz); 1030 Validate.notNull(childElementDef, "Unable to find element definition for class: %s", clazz); 1031 } 1032 1033 if (nextChild instanceof RuntimeChildDirectResource) { 1034 // Don't descend into embedded resources 1035 theCallback.acceptElement(theResource, nextValue, null, nextChild, childElementDef); 1036 } else { 1037 visit(theStack, theResource, nextValue, pathToElement, nextChild, childElementDef, theCallback); 1038 } 1039 } 1040 } 1041 } 1042 break; 1043 } 1044 case CONTAINED_RESOURCES: { 1045 BaseContainedDt value = (BaseContainedDt) theElement; 1046 for (IResource next : value.getContainedResources()) { 1047 def = myContext.getResourceDefinition(next); 1048 visit(theStack, next, next, pathToElement, null, def, theCallback); 1049 } 1050 break; 1051 } 1052 case CONTAINED_RESOURCE_LIST: 1053 case EXTENSION_DECLARED: 1054 case UNDECL_EXT: { 1055 throw new IllegalStateException(Msg.code(1795) + "state should not happen: " + def.getChildType()); 1056 } 1057 } 1058 1059 theStack.remove(theElement); 1060 1061 } 1062 1063 /** 1064 * Returns all embedded resources that are found embedded within <code>theResource</code>. 1065 * An embedded resource is a resource that can be found as a direct child within a resource, 1066 * as opposed to being referenced by the resource. 1067 * <p> 1068 * Examples include resources found within <code>Bundle.entry.resource</code> 1069 * and <code>Parameters.parameter.resource</code>, as well as contained resources 1070 * found within <code>Resource.contained</code> 1071 * </p> 1072 * 1073 * @param theRecurse Should embedded resources be recursively scanned for further embedded 1074 * resources 1075 * @return A collection containing the embedded resources. Order is arbitrary. 1076 */ 1077 public Collection<IBaseResource> getAllEmbeddedResources(IBaseResource theResource, boolean theRecurse) { 1078 Validate.notNull(theResource, "theResource must not be null"); 1079 ArrayList<IBaseResource> retVal = new ArrayList<>(); 1080 1081 visit(theResource, new IModelVisitor2() { 1082 @Override 1083 public boolean acceptElement(IBase theElement, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) { 1084 if (theElement == theResource) { 1085 return true; 1086 } 1087 if (theElement instanceof IBaseResource) { 1088 retVal.add((IBaseResource) theElement); 1089 return theRecurse; 1090 } 1091 return true; 1092 } 1093 1094 @Override 1095 public boolean acceptUndeclaredExtension(IBaseExtension<?, ?> theNextExt, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) { 1096 return true; 1097 } 1098 }); 1099 1100 return retVal; 1101 } 1102 1103 /** 1104 * Clear all content on a resource 1105 */ 1106 public void clear(IBaseResource theInput) { 1107 visit(theInput, new IModelVisitor2() { 1108 @Override 1109 public boolean acceptElement(IBase theElement, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) { 1110 if (theElement instanceof IPrimitiveType) { 1111 ((IPrimitiveType) theElement).setValueAsString(null); 1112 } 1113 return true; 1114 } 1115 1116 @Override 1117 public boolean acceptUndeclaredExtension(IBaseExtension<?, ?> theNextExt, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) { 1118 theNextExt.setUrl(null); 1119 theNextExt.setValue(null); 1120 return true; 1121 } 1122 1123 }); 1124 } 1125 1126 private void containResourcesForEncoding(ContainedResources theContained, IBaseResource theResource, boolean theModifyResource) { 1127 List<IBaseReference> allReferences = getAllPopulatedChildElementsOfType(theResource, IBaseReference.class); 1128 for (IBaseReference next : allReferences) { 1129 IBaseResource resource = next.getResource(); 1130 if (resource == null && next.getReferenceElement().isLocal()) { 1131 if (theContained.hasExistingIdToContainedResource()) { 1132 IBaseResource potentialTarget = theContained.getExistingIdToContainedResource().remove(next.getReferenceElement().getValue()); 1133 if (potentialTarget != null) { 1134 theContained.addContained(next.getReferenceElement(), potentialTarget); 1135 containResourcesForEncoding(theContained, potentialTarget, theModifyResource); 1136 } 1137 } 1138 } 1139 } 1140 1141 for (IBaseReference next : allReferences) { 1142 IBaseResource resource = next.getResource(); 1143 if (resource != null) { 1144 if (resource.getIdElement().isEmpty() || resource.getIdElement().isLocal()) { 1145 if (theContained.getResourceId(resource) != null) { 1146 // Prevent infinite recursion if there are circular loops in the contained resources 1147 continue; 1148 } 1149 IIdType id = theContained.addContained(resource); 1150 if (theModifyResource) { 1151 getContainedResourceList(theResource).add(resource); 1152 next.setReference(id.getValue()); 1153 } 1154 if (resource.getIdElement().isLocal() && theContained.hasExistingIdToContainedResource()) { 1155 theContained.getExistingIdToContainedResource().remove(resource.getIdElement().getValue()); 1156 } 1157 } 1158 1159 } 1160 1161 } 1162 1163 } 1164 1165 /** 1166 * Iterate through the whole resource and identify any contained resources. Optionally this method 1167 * can also assign IDs and modify references where the resource link has been specified but not the 1168 * reference text. 1169 * 1170 * @since 5.4.0 1171 */ 1172 public ContainedResources containResources(IBaseResource theResource, OptionsEnum... theOptions) { 1173 boolean storeAndReuse = false; 1174 boolean modifyResource = false; 1175 for (OptionsEnum next : theOptions) { 1176 switch (next) { 1177 case MODIFY_RESOURCE: 1178 modifyResource = true; 1179 break; 1180 case STORE_AND_REUSE_RESULTS: 1181 storeAndReuse = true; 1182 break; 1183 } 1184 } 1185 1186 if (storeAndReuse) { 1187 Object cachedValue = theResource.getUserData(USER_DATA_KEY_CONTAIN_RESOURCES_COMPLETED); 1188 if (cachedValue != null) { 1189 return (ContainedResources) cachedValue; 1190 } 1191 } 1192 1193 ContainedResources contained = new ContainedResources(); 1194 1195 List<? extends IBaseResource> containedResources = getContainedResourceList(theResource); 1196 for (IBaseResource next : containedResources) { 1197 String nextId = next.getIdElement().getValue(); 1198 if (StringUtils.isNotBlank(nextId)) { 1199 if (!nextId.startsWith("#")) { 1200 nextId = '#' + nextId; 1201 } 1202 next.getIdElement().setValue(nextId); 1203 } 1204 contained.addContained(next); 1205 } 1206 1207 if (myContext.getParserOptions().isAutoContainReferenceTargetsWithNoId()) { 1208 containResourcesForEncoding(contained, theResource, modifyResource); 1209 } 1210 1211 if (storeAndReuse) { 1212 theResource.setUserData(USER_DATA_KEY_CONTAIN_RESOURCES_COMPLETED, contained); 1213 } 1214 1215 return contained; 1216 } 1217 1218 @SuppressWarnings("unchecked") 1219 private <T extends IBaseResource> List<T> getContainedResourceList(T theResource) { 1220 List<T> containedResources = Collections.emptyList(); 1221 if (theResource instanceof IResource) { 1222 containedResources = (List<T>) ((IResource) theResource).getContained().getContainedResources(); 1223 } else if (theResource instanceof IDomainResource) { 1224 containedResources = (List<T>) ((IDomainResource) theResource).getContained(); 1225 } 1226 return containedResources; 1227 } 1228 1229 /** 1230 * Adds and returns a new element at the given path within the given structure. The paths used here 1231 * are <b>not FHIRPath expressions</b> but instead just simple dot-separated path expressions. 1232 * <p> 1233 * Only the last entry in the path is always created, existing repetitions of elements before 1234 * the final dot are returned if they exists (although they are created if they do not). For example, 1235 * given the path <code>Patient.name.given</code>, a new repetition of <code>given</code> is always 1236 * added to the first (index 0) repetition of the name. If an index-0 repetition of <code>name</code> 1237 * already exists, it is added to. If one does not exist, it if created and then added to. 1238 * </p> 1239 * <p> 1240 * If the last element in the path refers to a non-repeatable element that is already present and 1241 * is not empty, a {@link DataFormatException} error will be thrown. 1242 * </p> 1243 * 1244 * @param theTarget The element to add to. This will often be a {@link IBaseResource resource} 1245 * instance, but does not need to be. 1246 * @param thePath The path. 1247 * @return The newly added element 1248 * @throws DataFormatException If the path is invalid or does not end with either a repeatable element, or 1249 * an element that is non-repeatable but not already populated. 1250 */ 1251 @SuppressWarnings("unchecked") 1252 @Nonnull 1253 public <T extends IBase> T addElement(@Nonnull IBase theTarget, @Nonnull String thePath) { 1254 return (T) doAddElement(theTarget, thePath, 1).get(0); 1255 } 1256 1257 @SuppressWarnings("unchecked") 1258 private <T extends IBase> List<T> doAddElement(IBase theTarget, String thePath, int theElementsToAdd) { 1259 if (theElementsToAdd == 0) { 1260 return Collections.emptyList(); 1261 } 1262 1263 IBase target = theTarget; 1264 BaseRuntimeElementCompositeDefinition<?> def = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(target.getClass()); 1265 List<String> parts = parsePath(def, thePath); 1266 1267 for (int i = 0, partsSize = parts.size(); ; i++) { 1268 String nextPart = parts.get(i); 1269 boolean lastPart = i == partsSize - 1; 1270 1271 BaseRuntimeChildDefinition nextChild = def.getChildByName(nextPart); 1272 if (nextChild == null) { 1273 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(", "))); 1274 } 1275 1276 List<IBase> childValues = nextChild.getAccessor().getValues(target); 1277 IBase childValue; 1278 if (childValues.size() > 0 && !lastPart) { 1279 childValue = childValues.get(0); 1280 } else { 1281 1282 if (lastPart) { 1283 if (!childValues.isEmpty()) { 1284 if (theElementsToAdd == -1) { 1285 return (List<T>) Collections.singletonList(childValues.get(0)); 1286 } else if (nextChild.getMax() == 1 && !childValues.get(0).isEmpty()) { 1287 throw new DataFormatException(Msg.code(1797) + "Element at path " + thePath + " is not repeatable and not empty"); 1288 } else if (nextChild.getMax() == 1 && childValues.get(0).isEmpty()) { 1289 return (List<T>) Collections.singletonList(childValues.get(0)); 1290 } 1291 } 1292 } 1293 1294 BaseRuntimeElementDefinition<?> elementDef = nextChild.getChildByName(nextPart); 1295 childValue = elementDef.newInstance(nextChild.getInstanceConstructorArguments()); 1296 nextChild.getMutator().addValue(target, childValue); 1297 1298 if (lastPart) { 1299 if (theElementsToAdd == 1 || theElementsToAdd == -1) { 1300 return (List<T>) Collections.singletonList(childValue); 1301 } else { 1302 if (nextChild.getMax() == 1) { 1303 throw new DataFormatException(Msg.code(1798) + "Can not add multiple values at path " + thePath + ": Element does not repeat"); 1304 } 1305 1306 List<T> values = (List<T>) Lists.newArrayList(childValue); 1307 for (int j = 1; j < theElementsToAdd; j++) { 1308 childValue = elementDef.newInstance(nextChild.getInstanceConstructorArguments()); 1309 nextChild.getMutator().addValue(target, childValue); 1310 values.add((T) childValue); 1311 } 1312 1313 return values; 1314 } 1315 } 1316 1317 } 1318 1319 target = childValue; 1320 1321 if (!lastPart) { 1322 BaseRuntimeElementDefinition<?> nextDef = myContext.getElementDefinition(target.getClass()); 1323 if (!(nextDef instanceof BaseRuntimeElementCompositeDefinition)) { 1324 throw new DataFormatException(Msg.code(1799) + "Invalid path " + thePath + ": Element of type " + def.getName() + " has no child named " + nextPart + " (this is a primitive type)"); 1325 } 1326 def = (BaseRuntimeElementCompositeDefinition<?>) nextDef; 1327 } 1328 } 1329 1330 } 1331 1332 /** 1333 * Adds and returns a new element at the given path within the given structure. The paths used here 1334 * are <b>not FHIRPath expressions</b> but instead just simple dot-separated path expressions. 1335 * <p> 1336 * This method follows all of the same semantics as {@link #addElement(IBase, String)} but it 1337 * requires the path to point to an element with a primitive datatype and set the value of 1338 * the datatype to the given value. 1339 * </p> 1340 * 1341 * @param theTarget The element to add to. This will often be a {@link IBaseResource resource} 1342 * instance, but does not need to be. 1343 * @param thePath The path. 1344 * @param theValue The value to set, or <code>null</code>. 1345 * @return The newly added element 1346 * @throws DataFormatException If the path is invalid or does not end with either a repeatable element, or 1347 * an element that is non-repeatable but not already populated. 1348 */ 1349 @SuppressWarnings("unchecked") 1350 @Nonnull 1351 public <T extends IBase> T addElement(@Nonnull IBase theTarget, @Nonnull String thePath, @Nullable String theValue) { 1352 T value = (T) doAddElement(theTarget, thePath, 1).get(0); 1353 if (!(value instanceof IPrimitiveType)) { 1354 throw new DataFormatException(Msg.code(1800) + "Element at path " + thePath + " is not a primitive datatype. Found: " + myContext.getElementDefinition(value.getClass()).getName()); 1355 } 1356 1357 ((IPrimitiveType<?>) value).setValueAsString(theValue); 1358 1359 return value; 1360 } 1361 1362 1363 /** 1364 * Adds and returns a new element at the given path within the given structure. The paths used here 1365 * are <b>not FHIRPath expressions</b> but instead just simple dot-separated path expressions. 1366 * <p> 1367 * This method follows all of the same semantics as {@link #addElement(IBase, String)} but it 1368 * requires the path to point to an element with a primitive datatype and set the value of 1369 * the datatype to the given value. 1370 * </p> 1371 * 1372 * @param theTarget The element to add to. This will often be a {@link IBaseResource resource} 1373 * instance, but does not need to be. 1374 * @param thePath The path. 1375 * @param theValue The value to set, or <code>null</code>. 1376 * @return The newly added element 1377 * @throws DataFormatException If the path is invalid or does not end with either a repeatable element, or 1378 * an element that is non-repeatable but not already populated. 1379 */ 1380 @SuppressWarnings("unchecked") 1381 @Nonnull 1382 public <T extends IBase> T setElement(@Nonnull IBase theTarget, @Nonnull String thePath, @Nullable String theValue) { 1383 T value = (T) doAddElement(theTarget, thePath, -1).get(0); 1384 if (!(value instanceof IPrimitiveType)) { 1385 throw new DataFormatException(Msg.code(1801) + "Element at path " + thePath + " is not a primitive datatype. Found: " + myContext.getElementDefinition(value.getClass()).getName()); 1386 } 1387 1388 ((IPrimitiveType<?>) value).setValueAsString(theValue); 1389 1390 return value; 1391 } 1392 1393 1394 /** 1395 * This method has the same semantics as {@link #addElement(IBase, String, String)} but adds 1396 * a collection of primitives instead of a single one. 1397 * 1398 * @param theTarget The element to add to. This will often be a {@link IBaseResource resource} 1399 * instance, but does not need to be. 1400 * @param thePath The path. 1401 * @param theValues The values to set, or <code>null</code>. 1402 */ 1403 public void addElements(IBase theTarget, String thePath, Collection<String> theValues) { 1404 List<IBase> targets = doAddElement(theTarget, thePath, theValues.size()); 1405 Iterator<String> valuesIter = theValues.iterator(); 1406 for (IBase target : targets) { 1407 1408 if (!(target instanceof IPrimitiveType)) { 1409 throw new DataFormatException(Msg.code(1802) + "Element at path " + thePath + " is not a primitive datatype. Found: " + myContext.getElementDefinition(target.getClass()).getName()); 1410 } 1411 1412 ((IPrimitiveType<?>) target).setValueAsString(valuesIter.next()); 1413 } 1414 1415 } 1416 1417 /** 1418 * Clones a resource object, copying all data elements from theSource into a new copy of the same type. 1419 * <p> 1420 * Note that: 1421 * <ul> 1422 * <li>Only FHIR data elements are copied (i.e. user data maps are not copied)</li> 1423 * <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> 1424 * </ul> 1425 * 1426 * @param theSource The source resource 1427 * @return A copy of the source resource 1428 * @since 5.6.0 1429 */ 1430 @SuppressWarnings("unchecked") 1431 public <T extends IBaseResource> T clone(T theSource) { 1432 Validate.notNull(theSource, "theSource must not be null"); 1433 T target = (T) myContext.getResourceDefinition(theSource).newInstance(); 1434 cloneInto(theSource, target, false); 1435 return target; 1436 } 1437 1438 1439 public enum OptionsEnum { 1440 1441 /** 1442 * Should we modify the resource in the case that contained resource IDs are assigned 1443 * during a {@link #containResources(IBaseResource, OptionsEnum...)} pass. 1444 */ 1445 MODIFY_RESOURCE, 1446 1447 /** 1448 * Store the results of the operation in the resource metadata and reuse them if 1449 * subsequent calls are made. 1450 */ 1451 STORE_AND_REUSE_RESULTS 1452 } 1453 1454 public static class ContainedResources { 1455 private long myNextContainedId = 1; 1456 1457 private List<IBaseResource> myResourceList; 1458 private IdentityHashMap<IBaseResource, IIdType> myResourceToIdMap; 1459 private Map<String, IBaseResource> myExistingIdToContainedResourceMap; 1460 1461 public Map<String, IBaseResource> getExistingIdToContainedResource() { 1462 if (myExistingIdToContainedResourceMap == null) { 1463 myExistingIdToContainedResourceMap = new HashMap<>(); 1464 } 1465 return myExistingIdToContainedResourceMap; 1466 } 1467 1468 public IIdType addContained(IBaseResource theResource) { 1469 IIdType existing = getResourceToIdMap().get(theResource); 1470 if (existing != null) { 1471 return existing; 1472 } 1473 1474 IIdType newId = theResource.getIdElement(); 1475 if (isBlank(newId.getValue())) { 1476 newId.setValue("#" + myNextContainedId++); 1477 } else { 1478 // Avoid auto-assigned contained IDs colliding with pre-existing ones 1479 String idPart = newId.getValue(); 1480 if (substring(idPart, 0, 1).equals("#")) { 1481 idPart = idPart.substring(1); 1482 if (StringUtils.isNumeric(idPart)) { 1483 myNextContainedId = Long.parseLong(idPart) + 1; 1484 } 1485 } 1486 } 1487 1488 getResourceToIdMap().put(theResource, newId); 1489 getOrCreateResourceList().add(theResource); 1490 return newId; 1491 } 1492 1493 public void addContained(IIdType theId, IBaseResource theResource) { 1494 if (!getResourceToIdMap().containsKey(theResource)) { 1495 getResourceToIdMap().put(theResource, theId); 1496 getOrCreateResourceList().add(theResource); 1497 } 1498 } 1499 1500 public List<IBaseResource> getContainedResources() { 1501 if (getResourceToIdMap() == null) { 1502 return Collections.emptyList(); 1503 } 1504 return getOrCreateResourceList(); 1505 } 1506 1507 public IIdType getResourceId(IBaseResource theNext) { 1508 if (getResourceToIdMap() == null) { 1509 return null; 1510 } 1511 return getResourceToIdMap().get(theNext); 1512 } 1513 1514 private List<IBaseResource> getOrCreateResourceList() { 1515 if (myResourceList == null) { 1516 myResourceList = new ArrayList<>(); 1517 } 1518 return myResourceList; 1519 } 1520 1521 private IdentityHashMap<IBaseResource, IIdType> getResourceToIdMap() { 1522 if (myResourceToIdMap == null) { 1523 myResourceToIdMap = new IdentityHashMap<>(); 1524 } 1525 return myResourceToIdMap; 1526 } 1527 1528 public boolean isEmpty() { 1529 if (myResourceToIdMap == null) { 1530 return true; 1531 } 1532 return myResourceToIdMap.isEmpty(); 1533 } 1534 1535 public boolean hasExistingIdToContainedResource() { 1536 return myExistingIdToContainedResourceMap != null; 1537 } 1538 1539 public void assignIdsToContainedResources() { 1540 1541 if (!getContainedResources().isEmpty()) { 1542 1543 /* 1544 * The idea with the code block below: 1545 * 1546 * We want to preserve any IDs that were user-assigned, so that if it's really 1547 * important to someone that their contained resource have the ID of #FOO 1548 * or #1 we will keep that. 1549 * 1550 * For any contained resources where no ID was assigned by the user, we 1551 * want to manually create an ID but make sure we don't reuse an existing ID. 1552 */ 1553 1554 Set<String> ids = new HashSet<>(); 1555 1556 // Gather any user assigned IDs 1557 for (IBaseResource nextResource : getContainedResources()) { 1558 if (getResourceToIdMap().get(nextResource) != null) { 1559 ids.add(getResourceToIdMap().get(nextResource).getValue()); 1560 } 1561 } 1562 1563 // Automatically assign IDs to the rest 1564 for (IBaseResource nextResource : getContainedResources()) { 1565 1566 while (getResourceToIdMap().get(nextResource) == null) { 1567 String nextCandidate = "#" + myNextContainedId; 1568 myNextContainedId++; 1569 if (!ids.add(nextCandidate)) { 1570 continue; 1571 } 1572 1573 getResourceToIdMap().put(nextResource, new IdDt(nextCandidate)); 1574 } 1575 1576 } 1577 1578 } 1579 1580 } 1581 } 1582 1583}