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