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