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