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