001package ca.uhn.fhir.util; 002 003import ca.uhn.fhir.context.*; 004import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum; 005import ca.uhn.fhir.model.api.ExtensionDt; 006import ca.uhn.fhir.model.api.IResource; 007import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions; 008import ca.uhn.fhir.model.base.composite.BaseContainedDt; 009import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt; 010import ca.uhn.fhir.model.primitive.StringDt; 011import ca.uhn.fhir.parser.DataFormatException; 012import org.apache.commons.lang3.Validate; 013import org.hl7.fhir.instance.model.api.*; 014 015import java.util.*; 016import java.util.regex.Matcher; 017import java.util.regex.Pattern; 018import java.util.stream.Collectors; 019 020import static org.apache.commons.lang3.StringUtils.*; 021 022/* 023 * #%L 024 * HAPI FHIR - Core Library 025 * %% 026 * Copyright (C) 2014 - 2019 University Health Network 027 * %% 028 * Licensed under the Apache License, Version 2.0 (the "License"); 029 * you may not use this file except in compliance with the License. 030 * You may obtain a copy of the License at 031 * 032 * http://www.apache.org/licenses/LICENSE-2.0 033 * 034 * Unless required by applicable law or agreed to in writing, software 035 * distributed under the License is distributed on an "AS IS" BASIS, 036 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 037 * See the License for the specific language governing permissions and 038 * limitations under the License. 039 * #L% 040 */ 041 042public class FhirTerser { 043 044 private static final Pattern COMPARTMENT_MATCHER_PATH = Pattern.compile("([a-zA-Z.]+)\\.where\\(resolve\\(\\) is ([a-zA-Z]+)\\)"); 045 private FhirContext myContext; 046 047 public FhirTerser(FhirContext theContext) { 048 super(); 049 myContext = theContext; 050 } 051 052 private List<String> addNameToList(List<String> theCurrentList, BaseRuntimeChildDefinition theChildDefinition) { 053 if (theChildDefinition == null) 054 return null; 055 if (theCurrentList == null || theCurrentList.isEmpty()) 056 return new ArrayList<>(Collections.singletonList(theChildDefinition.getElementName())); 057 List<String> newList = new ArrayList<>(theCurrentList); 058 newList.add(theChildDefinition.getElementName()); 059 return newList; 060 } 061 062 private ExtensionDt createEmptyExtensionDt(IBaseExtension theBaseExtension, String theUrl) { 063 return createEmptyExtensionDt(theBaseExtension, false, theUrl); 064 } 065 066 @SuppressWarnings("unchecked") 067 private ExtensionDt createEmptyExtensionDt(IBaseExtension theBaseExtension, boolean theIsModifier, String theUrl) { 068 ExtensionDt retVal = new ExtensionDt(theIsModifier, theUrl); 069 theBaseExtension.getExtension().add(retVal); 070 return retVal; 071 } 072 073 private ExtensionDt createEmptyExtensionDt(ISupportsUndeclaredExtensions theSupportsUndeclaredExtensions, String theUrl) { 074 return createEmptyExtensionDt(theSupportsUndeclaredExtensions, false, theUrl); 075 } 076 077 private ExtensionDt createEmptyExtensionDt(ISupportsUndeclaredExtensions theSupportsUndeclaredExtensions, boolean theIsModifier, String theUrl) { 078 return theSupportsUndeclaredExtensions.addUndeclaredExtension(theIsModifier, theUrl); 079 } 080 081 private IBaseExtension createEmptyExtension(IBaseHasExtensions theBaseHasExtensions, String theUrl) { 082 return (IBaseExtension) theBaseHasExtensions.addExtension().setUrl(theUrl); 083 } 084 085 private IBaseExtension createEmptyModifierExtension(IBaseHasModifierExtensions theBaseHasModifierExtensions, String theUrl) { 086 return (IBaseExtension) theBaseHasModifierExtensions.addModifierExtension().setUrl(theUrl); 087 } 088 089 private ExtensionDt createEmptyModifierExtensionDt(ISupportsUndeclaredExtensions theSupportsUndeclaredExtensions, String theUrl) { 090 return createEmptyExtensionDt(theSupportsUndeclaredExtensions, true, theUrl); 091 } 092 093 /** 094 * Clones all values from a source object into the equivalent fields in a target object 095 * 096 * @param theSource The source object (must not be null) 097 * @param theTarget The target object to copy values into (must not be null) 098 * @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) 099 * @return Returns the target (which will be the same object that was passed into theTarget) for easy chaining 100 */ 101 public IBase cloneInto(IBase theSource, IBase theTarget, boolean theIgnoreMissingFields) { 102 Validate.notNull(theSource, "theSource must not be null"); 103 Validate.notNull(theTarget, "theTarget must not be null"); 104 105 if (theSource instanceof IPrimitiveType<?>) { 106 if (theTarget instanceof IPrimitiveType<?>) { 107 ((IPrimitiveType<?>) theTarget).setValueAsString(((IPrimitiveType<?>) theSource).getValueAsString()); 108 return theSource; 109 } 110 if (theIgnoreMissingFields) { 111 return theSource; 112 } 113 throw new DataFormatException("Can not copy value from primitive of type " + theSource.getClass().getName() + " into type " + theTarget.getClass().getName()); 114 } 115 116 BaseRuntimeElementCompositeDefinition<?> sourceDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theSource.getClass()); 117 BaseRuntimeElementCompositeDefinition<?> targetDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theTarget.getClass()); 118 119 List<BaseRuntimeChildDefinition> children = sourceDef.getChildren(); 120 if (sourceDef instanceof RuntimeExtensionDtDefinition) { 121 children = ((RuntimeExtensionDtDefinition) sourceDef).getChildrenIncludingUrl(); 122 } 123 124 for (BaseRuntimeChildDefinition nextChild : children) 125 for (IBase nextValue : nextChild.getAccessor().getValues(theSource)) { 126 String elementName = nextChild.getChildNameByDatatype(nextValue.getClass()); 127 BaseRuntimeChildDefinition targetChild = targetDef.getChildByName(elementName); 128 if (targetChild == null) { 129 if (theIgnoreMissingFields) { 130 continue; 131 } 132 throw new DataFormatException("Type " + theTarget.getClass().getName() + " does not have a child with name " + elementName); 133 } 134 135 BaseRuntimeElementDefinition<?> element = myContext.getElementDefinition(nextValue.getClass()); 136 IBase target = element.newInstance(); 137 138 targetChild.getMutator().addValue(theTarget, target); 139 cloneInto(nextValue, target, theIgnoreMissingFields); 140 } 141 142 return theTarget; 143 } 144 145 /** 146 * 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. 147 * <p> 148 * 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 149 * well as any contained resources. 150 * </p> 151 * <p> 152 * 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. 153 * {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource) 154 * </p> 155 * 156 * @param theResource The resource instance to search. Must not be null. 157 * @param theType The type to search for. Must not be null. 158 * @return Returns a list of all matching elements 159 */ 160 public <T extends IBase> List<T> getAllPopulatedChildElementsOfType(IBaseResource theResource, final Class<T> theType) { 161 final ArrayList<T> retVal = new ArrayList<>(); 162 BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource); 163 visit(new IdentityHashMap<>(), theResource, theResource, null, null, def, new IModelVisitor() { 164 @SuppressWarnings("unchecked") 165 @Override 166 public void acceptElement(IBaseResource theOuterResource, IBase theElement, List<String> thePathToElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition<?> theDefinition) { 167 if (theElement == null || theElement.isEmpty()) { 168 return; 169 } 170 171 if (theType.isAssignableFrom(theElement.getClass())) { 172 retVal.add((T) theElement); 173 } 174 } 175 }); 176 return retVal; 177 } 178 179 public List<ResourceReferenceInfo> getAllResourceReferences(final IBaseResource theResource) { 180 final ArrayList<ResourceReferenceInfo> retVal = new ArrayList<>(); 181 BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource); 182 visit(new IdentityHashMap<>(), theResource, theResource, null, null, def, new IModelVisitor() { 183 @Override 184 public void acceptElement(IBaseResource theOuterResource, IBase theElement, List<String> thePathToElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition<?> theDefinition) { 185 if (theElement == null || theElement.isEmpty()) { 186 return; 187 } 188 if (IBaseReference.class.isAssignableFrom(theElement.getClass())) { 189 retVal.add(new ResourceReferenceInfo(myContext, theOuterResource, thePathToElement, (IBaseReference) theElement)); 190 } 191 } 192 }); 193 return retVal; 194 } 195 196 private BaseRuntimeChildDefinition getDefinition(BaseRuntimeElementCompositeDefinition<?> theCurrentDef, List<String> theSubList) { 197 BaseRuntimeChildDefinition nextDef = theCurrentDef.getChildByNameOrThrowDataFormatException(theSubList.get(0)); 198 199 if (theSubList.size() == 1) { 200 return nextDef; 201 } 202 BaseRuntimeElementCompositeDefinition<?> cmp = (BaseRuntimeElementCompositeDefinition<?>) nextDef.getChildByName(theSubList.get(0)); 203 return getDefinition(cmp, theSubList.subList(1, theSubList.size())); 204 } 205 206 public BaseRuntimeChildDefinition getDefinition(Class<? extends IBaseResource> theResourceType, String thePath) { 207 RuntimeResourceDefinition def = myContext.getResourceDefinition(theResourceType); 208 209 List<String> parts = Arrays.asList(thePath.split("\\.")); 210 List<String> subList = parts.subList(1, parts.size()); 211 if (subList.size() < 1) { 212 throw new ConfigurationException("Invalid path: " + thePath); 213 } 214 return getDefinition(def, subList); 215 216 } 217 218 public Object getSingleValueOrNull(IBase theTarget, String thePath) { 219 Class<Object> wantedType = Object.class; 220 221 return getSingleValueOrNull(theTarget, thePath, wantedType); 222 } 223 224 public <T> T getSingleValueOrNull(IBase theTarget, String thePath, Class<T> theWantedType) { 225 Validate.notNull(theTarget, "theTarget must not be null"); 226 Validate.notBlank(thePath, "thePath must not be empty"); 227 228 BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(theTarget.getClass()); 229 if (!(def instanceof BaseRuntimeElementCompositeDefinition)) { 230 throw new IllegalArgumentException("Target is not a composite type: " + theTarget.getClass().getName()); 231 } 232 233 BaseRuntimeElementCompositeDefinition<?> currentDef = (BaseRuntimeElementCompositeDefinition<?>) def; 234 235 List<String> parts = parsePath(currentDef, thePath); 236 237 List<T> retVal = getValues(currentDef, theTarget, parts, theWantedType); 238 if (retVal.isEmpty()) { 239 return null; 240 } 241 return retVal.get(0); 242 } 243 244 private <T> List<T> getValues(BaseRuntimeElementCompositeDefinition<?> theCurrentDef, Object theCurrentObj, List<String> theSubList, Class<T> theWantedClass) { 245 return getValues(theCurrentDef, theCurrentObj, theSubList, theWantedClass, false, false); 246 } 247 248 @SuppressWarnings("unchecked") 249 private <T> List<T> getValues(BaseRuntimeElementCompositeDefinition<?> theCurrentDef, Object theCurrentObj, List<String> theSubList, Class<T> theWantedClass, boolean theCreate, boolean theAddExtension) { 250 String name = theSubList.get(0); 251 List<T> retVal = new ArrayList<>(); 252 253 if (name.startsWith("extension('")) { 254 String extensionUrl = name.substring("extension('".length()); 255 int endIndex = extensionUrl.indexOf('\''); 256 if (endIndex != -1) { 257 extensionUrl = extensionUrl.substring(0, endIndex); 258 } 259 260 if (myContext.getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3)) { 261 // DTSU2 262 final String extensionDtUrlForLambda = extensionUrl; 263 List<ExtensionDt> extensionDts = Collections.emptyList(); 264 if (theCurrentObj instanceof ISupportsUndeclaredExtensions) { 265 extensionDts = ((ISupportsUndeclaredExtensions) theCurrentObj).getUndeclaredExtensions() 266 .stream() 267 .filter(t -> t.getUrl().equals(extensionDtUrlForLambda)) 268 .collect(Collectors.toList()); 269 270 if (theAddExtension 271 && (!(theCurrentObj instanceof IBaseExtension) || (extensionDts.isEmpty() && theSubList.size() == 1))) { 272 extensionDts.add(createEmptyExtensionDt((ISupportsUndeclaredExtensions) theCurrentObj, extensionUrl)); 273 } 274 275 if (extensionDts.isEmpty() && theCreate) { 276 extensionDts.add(createEmptyExtensionDt((ISupportsUndeclaredExtensions) theCurrentObj, extensionUrl)); 277 } 278 279 } else if (theCurrentObj instanceof IBaseExtension) { 280 extensionDts = ((IBaseExtension) theCurrentObj).getExtension(); 281 282 if (theAddExtension 283 && (extensionDts.isEmpty() && theSubList.size() == 1)) { 284 extensionDts.add(createEmptyExtensionDt((IBaseExtension) theCurrentObj, extensionUrl)); 285 } 286 287 if (extensionDts.isEmpty() && theCreate) { 288 extensionDts.add(createEmptyExtensionDt((IBaseExtension) theCurrentObj, extensionUrl)); 289 } 290 } 291 292 for (ExtensionDt next : extensionDts) { 293 if (theWantedClass.isAssignableFrom(next.getClass())) { 294 retVal.add((T) next); 295 } 296 } 297 } else { 298 // DSTU3+ 299 final String extensionUrlForLambda = extensionUrl; 300 List<IBaseExtension> extensions = Collections.emptyList(); 301 if (theCurrentObj instanceof IBaseHasExtensions) { 302 extensions = ((IBaseHasExtensions) theCurrentObj).getExtension() 303 .stream() 304 .filter(t -> t.getUrl().equals(extensionUrlForLambda)) 305 .collect(Collectors.toList()); 306 307 if (theAddExtension 308 && (!(theCurrentObj instanceof IBaseExtension) || (extensions.isEmpty() && theSubList.size() == 1))) { 309 extensions.add(createEmptyExtension((IBaseHasExtensions) theCurrentObj, extensionUrl)); 310 } 311 312 if (extensions.isEmpty() && theCreate) { 313 extensions.add(createEmptyExtension((IBaseHasExtensions) theCurrentObj, extensionUrl)); 314 } 315 } 316 317 for (IBaseExtension next : extensions) { 318 if (theWantedClass.isAssignableFrom(next.getClass())) { 319 retVal.add((T) next); 320 } 321 } 322 } 323 324 if (theSubList.size() > 1) { 325 List<T> values = retVal; 326 retVal = new ArrayList<>(); 327 for (T nextElement : values) { 328 BaseRuntimeElementCompositeDefinition<?> nextChildDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition((Class<? extends IBase>) nextElement.getClass()); 329 List<T> foundValues = getValues(nextChildDef, nextElement, theSubList.subList(1, theSubList.size()), theWantedClass, theCreate, theAddExtension); 330 retVal.addAll(foundValues); 331 } 332 } 333 334 return retVal; 335 } 336 337 if (name.startsWith("modifierExtension('")) { 338 String extensionUrl = name.substring("modifierExtension('".length()); 339 int endIndex = extensionUrl.indexOf('\''); 340 if (endIndex != -1) { 341 extensionUrl = extensionUrl.substring(0, endIndex); 342 } 343 344 if (myContext.getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3)) { 345 // DSTU2 346 final String extensionDtUrlForLambda = extensionUrl; 347 List<ExtensionDt> extensionDts = Collections.emptyList(); 348 if (theCurrentObj instanceof ISupportsUndeclaredExtensions) { 349 extensionDts = ((ISupportsUndeclaredExtensions) theCurrentObj).getUndeclaredModifierExtensions() 350 .stream() 351 .filter(t -> t.getUrl().equals(extensionDtUrlForLambda)) 352 .collect(Collectors.toList()); 353 354 if (theAddExtension 355 && (!(theCurrentObj instanceof IBaseExtension) || (extensionDts.isEmpty() && theSubList.size() == 1))) { 356 extensionDts.add(createEmptyModifierExtensionDt((ISupportsUndeclaredExtensions) theCurrentObj, extensionUrl)); 357 } 358 359 if (extensionDts.isEmpty() && theCreate) { 360 extensionDts.add(createEmptyModifierExtensionDt((ISupportsUndeclaredExtensions) theCurrentObj, extensionUrl)); 361 } 362 363 } else if (theCurrentObj instanceof IBaseExtension) { 364 extensionDts = ((IBaseExtension) theCurrentObj).getExtension(); 365 366 if (theAddExtension 367 && (extensionDts.isEmpty() && theSubList.size() == 1)) { 368 extensionDts.add(createEmptyExtensionDt((IBaseExtension) theCurrentObj, extensionUrl)); 369 } 370 371 if (extensionDts.isEmpty() && theCreate) { 372 extensionDts.add(createEmptyExtensionDt((IBaseExtension) theCurrentObj, extensionUrl)); 373 } 374 } 375 376 for (ExtensionDt next : extensionDts) { 377 if (theWantedClass.isAssignableFrom(next.getClass())) { 378 retVal.add((T) next); 379 } 380 } 381 } else { 382 // DSTU3+ 383 final String extensionUrlForLambda = extensionUrl; 384 List<IBaseExtension> extensions = Collections.emptyList(); 385 386 if (theCurrentObj instanceof IBaseHasModifierExtensions) { 387 extensions = ((IBaseHasModifierExtensions) theCurrentObj).getModifierExtension() 388 .stream() 389 .filter(t -> t.getUrl().equals(extensionUrlForLambda)) 390 .collect(Collectors.toList()); 391 392 if (theAddExtension 393 && (!(theCurrentObj instanceof IBaseExtension) || (extensions.isEmpty() && theSubList.size() == 1))) { 394 extensions.add(createEmptyModifierExtension((IBaseHasModifierExtensions) theCurrentObj, extensionUrl)); 395 } 396 397 if (extensions.isEmpty() && theCreate) { 398 extensions.add(createEmptyModifierExtension((IBaseHasModifierExtensions) theCurrentObj, extensionUrl)); 399 } 400 } 401 402 for (IBaseExtension next : extensions) { 403 if (theWantedClass.isAssignableFrom(next.getClass())) { 404 retVal.add((T) next); 405 } 406 } 407 } 408 409 if (theSubList.size() > 1) { 410 List<T> values = retVal; 411 retVal = new ArrayList<>(); 412 for (T nextElement : values) { 413 BaseRuntimeElementCompositeDefinition<?> nextChildDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition((Class<? extends IBase>) nextElement.getClass()); 414 List<T> foundValues = getValues(nextChildDef, nextElement, theSubList.subList(1, theSubList.size()), theWantedClass, theCreate, theAddExtension); 415 retVal.addAll(foundValues); 416 } 417 } 418 419 return retVal; 420 } 421 422 BaseRuntimeChildDefinition nextDef = theCurrentDef.getChildByNameOrThrowDataFormatException(name); 423 List<? extends IBase> values = nextDef.getAccessor().getValues(theCurrentObj); 424 425 if (values.isEmpty() && theCreate) { 426 IBase value = nextDef.getChildByName(name).newInstance(); 427 nextDef.getMutator().addValue(theCurrentObj, value); 428 List<IBase> list = new ArrayList<>(); 429 list.add(value); 430 values = list; 431 } 432 433 if (theSubList.size() == 1) { 434 if (nextDef instanceof RuntimeChildChoiceDefinition) { 435 for (IBase next : values) { 436 if (next != null) { 437 if (name.endsWith("[x]")) { 438 if (theWantedClass == null || theWantedClass.isAssignableFrom(next.getClass())) { 439 retVal.add((T) next); 440 } 441 } else { 442 String childName = nextDef.getChildNameByDatatype(next.getClass()); 443 if (theSubList.get(0).equals(childName)) { 444 if (theWantedClass == null || theWantedClass.isAssignableFrom(next.getClass())) { 445 retVal.add((T) next); 446 } 447 } 448 } 449 } 450 } 451 } else { 452 for (IBase next : values) { 453 if (next != null) { 454 if (theWantedClass == null || theWantedClass.isAssignableFrom(next.getClass())) { 455 retVal.add((T) next); 456 } 457 } 458 } 459 } 460 } else { 461 for (IBase nextElement : values) { 462 BaseRuntimeElementCompositeDefinition<?> nextChildDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(nextElement.getClass()); 463 List<T> foundValues = getValues(nextChildDef, nextElement, theSubList.subList(1, theSubList.size()), theWantedClass, theCreate, theAddExtension); 464 retVal.addAll(foundValues); 465 } 466 } 467 return retVal; 468 } 469 470 /** 471 * Returns values stored in an element identified by its path. The list of values is of 472 * type {@link Object}. 473 * 474 * @param theResource The resource instance to be accessed. Must not be null. 475 * @param thePath The path for the element to be accessed. 476 * @return A list of values of type {@link Object}. 477 */ 478 public List<Object> getValues(IBaseResource theResource, String thePath) { 479 Class<Object> wantedClass = Object.class; 480 481 return getValues(theResource, thePath, wantedClass); 482 } 483 484 /** 485 * Returns values stored in an element identified by its path. The list of values is of 486 * type {@link Object}. 487 * 488 * @param theResource The resource instance to be accessed. Must not be null. 489 * @param thePath The path for the element to be accessed. 490 * @param theCreate When set to <code>true</code>, the terser will create a null-valued element where none exists. 491 * @return A list of values of type {@link Object}. 492 */ 493 public List<Object> getValues(IBaseResource theResource, String thePath, boolean theCreate) { 494 Class<Object> wantedClass = Object.class; 495 496 return getValues(theResource, thePath, wantedClass, theCreate); 497 } 498 499 /** 500 * Returns values stored in an element identified by its path. The list of values is of 501 * type {@link Object}. 502 * 503 * @param theResource The resource instance to be accessed. Must not be null. 504 * @param thePath The path for the element to be accessed. 505 * @param theCreate When set to <code>true</code>, the terser will create a null-valued element where none exists. 506 * @param theAddExtension When set to <code>true</code>, the terser will add a null-valued extension where one or more such extensions already exist. 507 * @return A list of values of type {@link Object}. 508 */ 509 public List<Object> getValues(IBaseResource theResource, String thePath, boolean theCreate, boolean theAddExtension) { 510 Class<Object> wantedClass = Object.class; 511 512 return getValues(theResource, thePath, wantedClass, theCreate, theAddExtension); 513 } 514 515 /** 516 * Returns values stored in an element identified by its path. The list of values is of 517 * type <code>theWantedClass</code>. 518 * 519 * @param theResource The resource instance to be accessed. Must not be null. 520 * @param thePath The path for the element to be accessed. 521 * @param theWantedClass The desired class to be returned in a list. 522 * @param <T> Type declared by <code>theWantedClass</code> 523 * @return A list of values of type <code>theWantedClass</code>. 524 */ 525 public <T> List<T> getValues(IBaseResource theResource, String thePath, Class<T> theWantedClass) { 526 RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource); 527 List<String> parts = parsePath(def, thePath); 528 return getValues(def, theResource, parts, theWantedClass); 529 } 530 531 /** 532 * Returns values stored in an element identified by its path. The list of values is of 533 * type <code>theWantedClass</code>. 534 * 535 * @param theResource The resource instance to be accessed. Must not be null. 536 * @param thePath The path for the element to be accessed. 537 * @param theWantedClass The desired class to be returned in a list. 538 * @param theCreate When set to <code>true</code>, the terser will create a null-valued element where none exists. 539 * @param <T> Type declared by <code>theWantedClass</code> 540 * @return A list of values of type <code>theWantedClass</code>. 541 */ 542 public <T> List<T> getValues(IBaseResource theResource, String thePath, Class<T> theWantedClass, boolean theCreate) { 543 RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource); 544 List<String> parts = parsePath(def, thePath); 545 return getValues(def, theResource, parts, theWantedClass, theCreate, false); 546 } 547 548 /** 549 * Returns values stored in an element identified by its path. The list of values is of 550 * type <code>theWantedClass</code>. 551 * 552 * @param theResource The resource instance to be accessed. Must not be null. 553 * @param thePath The path for the element to be accessed. 554 * @param theWantedClass The desired class to be returned in a list. 555 * @param theCreate When set to <code>true</code>, the terser will create a null-valued element where none exists. 556 * @param theAddExtension When set to <code>true</code>, the terser will add a null-valued extension where one or more such extensions already exist. 557 * @param <T> Type declared by <code>theWantedClass</code> 558 * @return A list of values of type <code>theWantedClass</code>. 559 */ 560 public <T> List<T> getValues(IBaseResource theResource, String thePath, Class<T> theWantedClass, boolean theCreate, boolean theAddExtension) { 561 RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource); 562 List<String> parts = parsePath(def, thePath); 563 return getValues(def, theResource, parts, theWantedClass, theCreate, theAddExtension); 564 } 565 566 private List<String> parsePath(BaseRuntimeElementCompositeDefinition<?> theElementDef, String thePath) { 567 List<String> parts = new ArrayList<>(); 568 569 int currentStart = 0; 570 boolean inSingleQuote = false; 571 for (int i = 0; i < thePath.length(); i++) { 572 switch (thePath.charAt(i)) { 573 case '\'': 574 inSingleQuote = !inSingleQuote; 575 break; 576 case '.': 577 if (!inSingleQuote) { 578 parts.add(thePath.substring(currentStart, i)); 579 currentStart = i + 1; 580 } 581 break; 582 } 583 } 584 585 parts.add(thePath.substring(currentStart)); 586 587 if (theElementDef instanceof RuntimeResourceDefinition) { 588 if (parts.size() > 0 && parts.get(0).equals(theElementDef.getName())) { 589 parts = parts.subList(1, parts.size()); 590 } 591 } 592 593 if (parts.size() < 1) { 594 throw new ConfigurationException("Invalid path: " + thePath); 595 } 596 return parts; 597 } 598 599 /** 600 * Returns <code>true</code> if <code>theSource</code> is in the compartment named <code>theCompartmentName</code> 601 * belonging to resource <code>theTarget</code> 602 * 603 * @param theCompartmentName The name of the compartment 604 * @param theSource The potential member of the compartment 605 * @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} 606 * @return <code>true</code> if <code>theSource</code> is in the compartment 607 * @throws IllegalArgumentException If theTarget does not contain both a resource type and ID 608 */ 609 public boolean isSourceInCompartmentForTarget(String theCompartmentName, IBaseResource theSource, IIdType theTarget) { 610 Validate.notBlank(theCompartmentName, "theCompartmentName must not be null or blank"); 611 Validate.notNull(theSource, "theSource must not be null"); 612 Validate.notNull(theTarget, "theTarget must not be null"); 613 Validate.notBlank(defaultString(theTarget.getResourceType()), "theTarget must have a populated resource type (theTarget.getResourceType() does not return a value)"); 614 Validate.notBlank(defaultString(theTarget.getIdPart()), "theTarget must have a populated ID (theTarget.getIdPart() does not return a value)"); 615 616 String wantRef = theTarget.toUnqualifiedVersionless().getValue(); 617 618 RuntimeResourceDefinition sourceDef = myContext.getResourceDefinition(theSource); 619 if (theSource.getIdElement().hasIdPart()) { 620 if (wantRef.equals(sourceDef.getName() + '/' + theSource.getIdElement().getIdPart())) { 621 return true; 622 } 623 } 624 625 List<RuntimeSearchParam> params = sourceDef.getSearchParamsForCompartmentName(theCompartmentName); 626 for (RuntimeSearchParam nextParam : params) { 627 for (String nextPath : nextParam.getPathsSplit()) { 628 629 /* 630 * DSTU3 and before just defined compartments as being (e.g.) named 631 * Patient with a path like CarePlan.subject 632 * 633 * R4 uses a fancier format like CarePlan.subject.where(resolve() is Patient) 634 * 635 * The following Regex is a hack to make that efficient at runtime. 636 */ 637 String wantType = null; 638 Pattern pattern = COMPARTMENT_MATCHER_PATH; 639 Matcher matcher = pattern.matcher(nextPath); 640 if (matcher.matches()) { 641 nextPath = matcher.group(1); 642 wantType = matcher.group(2); 643 } 644 645 List<IBaseReference> values = getValues(theSource, nextPath, IBaseReference.class); 646 for (IBaseReference nextValue : values) { 647 IIdType nextTargetId = nextValue.getReferenceElement(); 648 String nextRef = nextTargetId.toUnqualifiedVersionless().getValue(); 649 650 /* 651 * If the reference isn't an explicit resource ID, but instead is just 652 * a resource object, we'll calculate its ID and treat the target 653 * as that. 654 */ 655 if (isBlank(nextRef) && nextValue.getResource() != null) { 656 IBaseResource nextTarget = nextValue.getResource(); 657 nextTargetId = nextTarget.getIdElement().toUnqualifiedVersionless(); 658 if (!nextTargetId.hasResourceType()) { 659 String resourceType = myContext.getResourceDefinition(nextTarget).getName(); 660 nextTargetId.setParts(null, resourceType, nextTargetId.getIdPart(), null); 661 } 662 nextRef = nextTargetId.getValue(); 663 } 664 665 if (isNotBlank(wantType)) { 666 String nextTargetIdResourceType = nextTargetId.getResourceType(); 667 if (nextTargetIdResourceType == null || !nextTargetIdResourceType.equals(wantType)) { 668 continue; 669 } 670 } 671 672 if (wantRef.equals(nextRef)) { 673 return true; 674 } 675 } 676 } 677 } 678 679 return false; 680 } 681 682 private void visit(IBase theElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition<?> theDefinition, IModelVisitor2 theCallback, List<IBase> theContainingElementPath, 683 List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) { 684 if (theChildDefinition != null) { 685 theChildDefinitionPath.add(theChildDefinition); 686 } 687 theContainingElementPath.add(theElement); 688 theElementDefinitionPath.add(theDefinition); 689 690 boolean recurse = theCallback.acceptElement(theElement, Collections.unmodifiableList(theContainingElementPath), Collections.unmodifiableList(theChildDefinitionPath), 691 Collections.unmodifiableList(theElementDefinitionPath)); 692 if (recurse) { 693 694 /* 695 * Visit undeclared extensions 696 */ 697 if (theElement instanceof ISupportsUndeclaredExtensions) { 698 ISupportsUndeclaredExtensions containingElement = (ISupportsUndeclaredExtensions) theElement; 699 for (ExtensionDt nextExt : containingElement.getUndeclaredExtensions()) { 700 theContainingElementPath.add(nextExt); 701 theCallback.acceptUndeclaredExtension(nextExt, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath); 702 theContainingElementPath.remove(theContainingElementPath.size() - 1); 703 } 704 } 705 706 /* 707 * Now visit the children of the given element 708 */ 709 switch (theDefinition.getChildType()) { 710 case ID_DATATYPE: 711 case PRIMITIVE_XHTML_HL7ORG: 712 case PRIMITIVE_XHTML: 713 case PRIMITIVE_DATATYPE: 714 // These are primitive types, so we don't need to visit their children 715 break; 716 case RESOURCE: 717 case RESOURCE_BLOCK: 718 case COMPOSITE_DATATYPE: { 719 BaseRuntimeElementCompositeDefinition<?> childDef = (BaseRuntimeElementCompositeDefinition<?>) theDefinition; 720 for (BaseRuntimeChildDefinition nextChild : childDef.getChildrenAndExtension()) { 721 List<? extends IBase> values = nextChild.getAccessor().getValues(theElement); 722 if (values != null) { 723 for (IBase nextValue : values) { 724 if (nextValue == null) { 725 continue; 726 } 727 if (nextValue.isEmpty()) { 728 continue; 729 } 730 BaseRuntimeElementDefinition<?> childElementDef; 731 childElementDef = nextChild.getChildElementDefinitionByDatatype(nextValue.getClass()); 732 733 if (childElementDef == null) { 734 StringBuilder b = new StringBuilder(); 735 b.append("Found value of type["); 736 b.append(nextValue.getClass().getSimpleName()); 737 b.append("] which is not valid for field["); 738 b.append(nextChild.getElementName()); 739 b.append("] in "); 740 b.append(childDef.getName()); 741 b.append(" - Valid types: "); 742 for (Iterator<String> iter = new TreeSet<>(nextChild.getValidChildNames()).iterator(); iter.hasNext(); ) { 743 BaseRuntimeElementDefinition<?> childByName = nextChild.getChildByName(iter.next()); 744 b.append(childByName.getImplementingClass().getSimpleName()); 745 if (iter.hasNext()) { 746 b.append(", "); 747 } 748 } 749 throw new DataFormatException(b.toString()); 750 } 751 752 visit(nextValue, nextChild, childElementDef, theCallback, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath); 753 } 754 } 755 } 756 break; 757 } 758 case CONTAINED_RESOURCES: { 759 BaseContainedDt value = (BaseContainedDt) theElement; 760 for (IResource next : value.getContainedResources()) { 761 BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(next); 762 visit(next, null, def, theCallback, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath); 763 } 764 break; 765 } 766 case EXTENSION_DECLARED: 767 case UNDECL_EXT: { 768 throw new IllegalStateException("state should not happen: " + theDefinition.getChildType()); 769 } 770 case CONTAINED_RESOURCE_LIST: { 771 if (theElement != null) { 772 BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(theElement.getClass()); 773 visit(theElement, null, def, theCallback, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath); 774 } 775 break; 776 } 777 } 778 779 } 780 781 if (theChildDefinition != null) { 782 theChildDefinitionPath.remove(theChildDefinitionPath.size() - 1); 783 } 784 theContainingElementPath.remove(theContainingElementPath.size() - 1); 785 theElementDefinitionPath.remove(theElementDefinitionPath.size() - 1); 786 } 787 788 /** 789 * Visit all elements in a given resource 790 * 791 * <p> 792 * 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. 793 * {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource) 794 * </p> 795 * 796 * @param theResource The resource to visit 797 * @param theVisitor The visitor 798 */ 799 public void visit(IBaseResource theResource, IModelVisitor theVisitor) { 800 BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource); 801 visit(new IdentityHashMap<>(), theResource, theResource, null, null, def, theVisitor); 802 } 803 804 /** 805 * Visit all elements in a given resource 806 * <p> 807 * <b>THIS ALTERNATE METHOD IS STILL EXPERIMENTAL! USE WITH CAUTION</b> 808 * </p> 809 * <p> 810 * 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. 811 * {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource) 812 * </p> 813 * 814 * @param theResource The resource to visit 815 * @param theVisitor The visitor 816 */ 817 public void visit(IBaseResource theResource, IModelVisitor2 theVisitor) { 818 BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource); 819 visit(theResource, null, def, theVisitor, new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); 820 } 821 822 private void visit(IdentityHashMap<Object, Object> theStack, IBaseResource theResource, IBase theElement, List<String> thePathToElement, BaseRuntimeChildDefinition theChildDefinition, 823 BaseRuntimeElementDefinition<?> theDefinition, IModelVisitor theCallback) { 824 List<String> pathToElement = addNameToList(thePathToElement, theChildDefinition); 825 826 if (theStack.put(theElement, theElement) != null) { 827 return; 828 } 829 830 theCallback.acceptElement(theResource, theElement, pathToElement, theChildDefinition, theDefinition); 831 832 BaseRuntimeElementDefinition<?> def = theDefinition; 833 if (def.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST) { 834 def = myContext.getElementDefinition(theElement.getClass()); 835 } 836 837 if (theElement instanceof IBaseReference) { 838 IBaseResource target = ((IBaseReference) theElement).getResource(); 839 if (target != null) { 840 if (target.getIdElement().hasIdPart() == false || target.getIdElement().isLocal()) { 841 RuntimeResourceDefinition targetDef = myContext.getResourceDefinition(target); 842 visit(theStack, target, target, pathToElement, null, targetDef, theCallback); 843 } 844 } 845 } 846 847 switch (def.getChildType()) { 848 case ID_DATATYPE: 849 case PRIMITIVE_XHTML_HL7ORG: 850 case PRIMITIVE_XHTML: 851 case PRIMITIVE_DATATYPE: 852 // These are primitive types 853 break; 854 case RESOURCE: 855 case RESOURCE_BLOCK: 856 case COMPOSITE_DATATYPE: { 857 BaseRuntimeElementCompositeDefinition<?> childDef = (BaseRuntimeElementCompositeDefinition<?>) def; 858 for (BaseRuntimeChildDefinition nextChild : childDef.getChildrenAndExtension()) { 859 860 List<?> values = nextChild.getAccessor().getValues(theElement); 861 if (values != null) { 862 for (Object nextValueObject : values) { 863 IBase nextValue; 864 try { 865 nextValue = (IBase) nextValueObject; 866 } catch (ClassCastException e) { 867 String s = "Found instance of " + nextValueObject.getClass() + " - Did you set a field value to the incorrect type? Expected " + IBase.class.getName(); 868 throw new ClassCastException(s); 869 } 870 if (nextValue == null) { 871 continue; 872 } 873 if (nextValue.isEmpty()) { 874 continue; 875 } 876 BaseRuntimeElementDefinition<?> childElementDef; 877 childElementDef = nextChild.getChildElementDefinitionByDatatype(nextValue.getClass()); 878 879 if (childElementDef == null) { 880 childElementDef = myContext.getElementDefinition(nextValue.getClass()); 881 } 882 883 if (nextChild instanceof RuntimeChildDirectResource) { 884 // Don't descend into embedded resources 885 theCallback.acceptElement(theResource, nextValue, null, nextChild, childElementDef); 886 } else { 887 visit(theStack, theResource, nextValue, pathToElement, nextChild, childElementDef, theCallback); 888 } 889 } 890 } 891 } 892 break; 893 } 894 case CONTAINED_RESOURCES: { 895 BaseContainedDt value = (BaseContainedDt) theElement; 896 for (IResource next : value.getContainedResources()) { 897 def = myContext.getResourceDefinition(next); 898 visit(theStack, next, next, pathToElement, null, def, theCallback); 899 } 900 break; 901 } 902 case CONTAINED_RESOURCE_LIST: 903 case EXTENSION_DECLARED: 904 case UNDECL_EXT: { 905 throw new IllegalStateException("state should not happen: " + def.getChildType()); 906 } 907 } 908 909 theStack.remove(theElement); 910 911 } 912 913 /** 914 * Returns all embedded resources that are found embedded within <code>theResource</code>. 915 * An embedded resource is a resource that can be found as a direct child within a resource, 916 * as opposed to being referenced by the resource. 917 * <p> 918 * Examples include resources found within <code>Bundle.entry.resource</code> 919 * and <code>Parameters.parameter.resource</code>, as well as contained resources 920 * found within <code>Resource.contained</code> 921 * </p> 922 * 923 * @param theRecurse Should embedded resources be recursively scanned for further embedded 924 * resources 925 * @return A collection containing the embedded resources. Order is arbitrary. 926 */ 927 public Collection<IBaseResource> getAllEmbeddedResources(IBaseResource theResource, boolean theRecurse) { 928 Validate.notNull(theResource, "theResource must not be null"); 929 ArrayList<IBaseResource> retVal = new ArrayList<>(); 930 931 visit(theResource, new IModelVisitor2() { 932 @Override 933 public boolean acceptElement(IBase theElement, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) { 934 if (theElement == theResource) { 935 return true; 936 } 937 if (theElement instanceof IBaseResource) { 938 retVal.add((IBaseResource) theElement); 939 return theRecurse; 940 } 941 return true; 942 } 943 944 @Override 945 public boolean acceptUndeclaredExtension(IBaseExtension<?, ?> theNextExt, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) { 946 return true; 947 } 948 }); 949 950 return retVal; 951 } 952 953 /** 954 * Clear all content on a resource 955 */ 956 public void clear(IBaseResource theInput) { 957 visit(theInput, new IModelVisitor2() { 958 @Override 959 public boolean acceptElement(IBase theElement, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) { 960 if (theElement instanceof IPrimitiveType) { 961 ((IPrimitiveType) theElement).setValueAsString(null); 962 } 963 return true; 964 } 965 966 @Override 967 public boolean acceptUndeclaredExtension(IBaseExtension<?, ?> theNextExt, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) { 968 theNextExt.setUrl(null); 969 theNextExt.setValue(null); 970 return true; 971 } 972 973 }); 974 } 975}