001package ca.uhn.fhir.util; 002 003import static org.apache.commons.lang3.StringUtils.defaultString; 004import static org.apache.commons.lang3.StringUtils.isBlank; 005 006/* 007 * #%L 008 * HAPI FHIR - Core Library 009 * %% 010 * Copyright (C) 2014 - 2018 University Health Network 011 * %% 012 * Licensed under the Apache License, Version 2.0 (the "License"); 013 * you may not use this file except in compliance with the License. 014 * You may obtain a copy of the License at 015 * 016 * http://www.apache.org/licenses/LICENSE-2.0 017 * 018 * Unless required by applicable law or agreed to in writing, software 019 * distributed under the License is distributed on an "AS IS" BASIS, 020 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 021 * See the License for the specific language governing permissions and 022 * limitations under the License. 023 * #L% 024 */ 025import java.util.*; 026import java.util.function.Predicate; 027 028import org.apache.commons.lang3.Validate; 029import org.hl7.fhir.instance.model.api.*; 030 031import ca.uhn.fhir.context.*; 032import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum; 033import ca.uhn.fhir.model.api.ExtensionDt; 034import ca.uhn.fhir.model.api.IResource; 035import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions; 036import ca.uhn.fhir.model.base.composite.BaseContainedDt; 037import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt; 038import ca.uhn.fhir.model.primitive.StringDt; 039import ca.uhn.fhir.parser.DataFormatException; 040 041public class FhirTerser { 042 043 private FhirContext myContext; 044 045 public FhirTerser(FhirContext theContext) { 046 super(); 047 myContext = theContext; 048 } 049 050 private List<String> addNameToList(List<String> theCurrentList, BaseRuntimeChildDefinition theChildDefinition) { 051 if (theChildDefinition == null) 052 return null; 053 if (theCurrentList == null || theCurrentList.isEmpty()) 054 return new ArrayList<String>(Arrays.asList(theChildDefinition.getElementName())); 055 List<String> newList = new ArrayList<String>(theCurrentList); 056 newList.add(theChildDefinition.getElementName()); 057 return newList; 058 } 059 060 061 /** 062 * Clones all values from a source object into the equivalent fields in a target object 063 * @param theSource The source object (must not be null) 064 * @param theTarget The target object to copy values into (must not be null) 065 * @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) 066 */ 067 public void cloneInto(IBase theSource, IBase theTarget, boolean theIgnoreMissingFields) { 068 Validate.notNull(theSource, "theSource must not be null"); 069 Validate.notNull(theTarget, "theTarget must not be null"); 070 071 if (theSource instanceof IPrimitiveType<?>) { 072 if (theTarget instanceof IPrimitiveType<?>) { 073 ((IPrimitiveType<?>)theTarget).setValueAsString(((IPrimitiveType<?>)theSource).getValueAsString()); 074 return; 075 } 076 if (theIgnoreMissingFields) { 077 return; 078 } 079 throw new DataFormatException("Can not copy value from primitive of type " + theSource.getClass().getName() + " into type " + theTarget.getClass().getName()); 080 } 081 082 BaseRuntimeElementCompositeDefinition<?> sourceDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theSource.getClass()); 083 BaseRuntimeElementCompositeDefinition<?> targetDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theTarget.getClass()); 084 085 List<BaseRuntimeChildDefinition> children = sourceDef.getChildren(); 086 if (sourceDef instanceof RuntimeExtensionDtDefinition) { 087 children = ((RuntimeExtensionDtDefinition)sourceDef).getChildrenIncludingUrl(); 088 } 089 090 for (BaseRuntimeChildDefinition nextChild : children) { 091 for (IBase nextValue : nextChild.getAccessor().getValues(theSource)) { 092 String elementName = nextChild.getChildNameByDatatype(nextValue.getClass()); 093 BaseRuntimeChildDefinition targetChild = targetDef.getChildByName(elementName); 094 if (targetChild == null) { 095 if (theIgnoreMissingFields) { 096 continue; 097 } 098 throw new DataFormatException("Type " + theTarget.getClass().getName() + " does not have a child with name " + elementName); 099 } 100 101 BaseRuntimeElementDefinition<?> childDef = targetChild.getChildByName(elementName); 102 IBase target = childDef.newInstance(); 103 targetChild.getMutator().addValue(theTarget, target); 104 cloneInto(nextValue, target, theIgnoreMissingFields); 105 } 106 } 107 108 } 109 110 /** 111 * 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. 112 * <p> 113 * 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 114 * well as any contained resources. 115 * </p> 116 * <p> 117 * 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. 118 * {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource) 119 * </p> 120 * 121 * @param theResource 122 * The resource instance to search. Must not be null. 123 * @param theType 124 * The type to search for. Must not be null. 125 * @return Returns a list of all matching elements 126 */ 127 public <T extends IBase> List<T> getAllPopulatedChildElementsOfType(IBaseResource theResource, final Class<T> theType) { 128 final ArrayList<T> retVal = new ArrayList<T>(); 129 BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource); 130 visit(new IdentityHashMap<Object, Object>(), theResource, theResource, null, null, def, new IModelVisitor() { 131 @SuppressWarnings("unchecked") 132 @Override 133 public void acceptElement(IBaseResource theOuterResource, IBase theElement, List<String> thePathToElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition<?> theDefinition) { 134 if (theElement == null || theElement.isEmpty()) { 135 return; 136 } 137 138 if (theType.isAssignableFrom(theElement.getClass())) { 139 retVal.add((T) theElement); 140 } 141 } 142 }); 143 return retVal; 144 } 145 146 public List<ResourceReferenceInfo> getAllResourceReferences(final IBaseResource theResource) { 147 final ArrayList<ResourceReferenceInfo> retVal = new ArrayList<ResourceReferenceInfo>(); 148 BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource); 149 visit(new IdentityHashMap<Object, Object>(), theResource, theResource, null, null, def, new IModelVisitor() { 150 @Override 151 public void acceptElement(IBaseResource theOuterResource, IBase theElement, List<String> thePathToElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition<?> theDefinition) { 152 if (theElement == null || theElement.isEmpty()) { 153 return; 154 } 155 if (IBaseReference.class.isAssignableFrom(theElement.getClass())) { 156 retVal.add(new ResourceReferenceInfo(myContext, theOuterResource, thePathToElement, (IBaseReference) theElement)); 157 } 158 } 159 }); 160 return retVal; 161 } 162 163 private BaseRuntimeChildDefinition getDefinition(BaseRuntimeElementCompositeDefinition<?> theCurrentDef, List<String> theSubList) { 164 BaseRuntimeChildDefinition nextDef = theCurrentDef.getChildByNameOrThrowDataFormatException(theSubList.get(0)); 165 166 if (theSubList.size() == 1) { 167 return nextDef; 168 } 169 BaseRuntimeElementCompositeDefinition<?> cmp = (BaseRuntimeElementCompositeDefinition<?>) nextDef.getChildByName(theSubList.get(0)); 170 return getDefinition(cmp, theSubList.subList(1, theSubList.size())); 171 } 172 173 public BaseRuntimeChildDefinition getDefinition(Class<? extends IBaseResource> theResourceType, String thePath) { 174 RuntimeResourceDefinition def = myContext.getResourceDefinition(theResourceType); 175 176 BaseRuntimeElementCompositeDefinition<?> currentDef = def; 177 178 List<String> parts = Arrays.asList(thePath.split("\\.")); 179 List<String> subList = parts.subList(1, parts.size()); 180 if (subList.size() < 1) { 181 throw new ConfigurationException("Invalid path: " + thePath); 182 } 183 return getDefinition(currentDef, subList); 184 185 } 186 187 public Object getSingleValueOrNull(IBase theTarget, String thePath) { 188 Class<Object> wantedType = Object.class; 189 190 return getSingleValueOrNull(theTarget, thePath, wantedType); 191 } 192 193 public <T> T getSingleValueOrNull(IBase theTarget, String thePath, Class<T> theWantedType) { 194 Validate.notNull(theTarget, "theTarget must not be null"); 195 Validate.notBlank(thePath, "thePath must not be empty"); 196 197 BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(theTarget.getClass()); 198 if (!(def instanceof BaseRuntimeElementCompositeDefinition)) { 199 throw new IllegalArgumentException("Target is not a composite type: " + theTarget.getClass().getName()); 200 } 201 202 BaseRuntimeElementCompositeDefinition<?> currentDef = (BaseRuntimeElementCompositeDefinition<?>) def; 203 Object currentObj = theTarget; 204 205 List<String> parts = parsePath(currentDef, thePath); 206 207 List<T> retVal = getValues(currentDef, currentObj, parts, theWantedType); 208 if (retVal.isEmpty()) { 209 return null; 210 } 211 return retVal.get(0); 212 } 213 214 @SuppressWarnings("unchecked") 215 private <T> List<T> getValues(BaseRuntimeElementCompositeDefinition<?> theCurrentDef, Object theCurrentObj, List<String> theSubList, Class<T> theWantedClass) { 216 String name = theSubList.get(0); 217 List<T> retVal = new ArrayList<>(); 218 219 if (name.startsWith("extension('")) { 220 String extensionUrl = name.substring("extension('".length()); 221 int endIndex = extensionUrl.indexOf('\''); 222 if (endIndex != -1) { 223 extensionUrl = extensionUrl.substring(0, endIndex); 224 } 225 226 List<ExtensionDt> extensions= Collections.emptyList(); 227 if (theCurrentObj instanceof ISupportsUndeclaredExtensions) { 228 extensions = ((ISupportsUndeclaredExtensions) theCurrentObj).getUndeclaredExtensionsByUrl(extensionUrl); 229 } else if (theCurrentObj instanceof IBaseExtension) { 230 extensions = ((IBaseExtension)theCurrentObj).getExtension(); 231 } 232 233 for (ExtensionDt next : extensions) { 234 if (theWantedClass.isAssignableFrom(next.getClass())) { 235 retVal.add((T) next); 236 } 237 } 238 239 if (theSubList.size() > 1) { 240 List<T> values = retVal; 241 retVal = new ArrayList<>(); 242 for (T nextElement : values) { 243 BaseRuntimeElementCompositeDefinition<?> nextChildDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition((Class<? extends IBase>) nextElement.getClass()); 244 List<T> foundValues = getValues(nextChildDef, nextElement, theSubList.subList(1, theSubList.size()), theWantedClass); 245 retVal.addAll(foundValues); 246 } 247 } 248 249 return retVal; 250 } 251 252 BaseRuntimeChildDefinition nextDef = theCurrentDef.getChildByNameOrThrowDataFormatException(name); 253 List<? extends IBase> values = nextDef.getAccessor().getValues(theCurrentObj); 254 255 if (theSubList.size() == 1) { 256 if (nextDef instanceof RuntimeChildChoiceDefinition) { 257 for (IBase next : values) { 258 if (next != null) { 259 if (name.endsWith("[x]")) { 260 if (theWantedClass == null || theWantedClass.isAssignableFrom(next.getClass())) { 261 retVal.add((T) next); 262 } 263 } else { 264 String childName = nextDef.getChildNameByDatatype(next.getClass()); 265 if (theSubList.get(0).equals(childName)) { 266 if (theWantedClass == null || theWantedClass.isAssignableFrom(next.getClass())) { 267 retVal.add((T) next); 268 } 269 } 270 } 271 } 272 } 273 } else { 274 for (IBase next : values) { 275 if (next != null) { 276 if (theWantedClass == null || theWantedClass.isAssignableFrom(next.getClass())) { 277 retVal.add((T) next); 278 } 279 } 280 } 281 } 282 } else { 283 for (IBase nextElement : values) { 284 BaseRuntimeElementCompositeDefinition<?> nextChildDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(nextElement.getClass()); 285 List<T> foundValues = getValues(nextChildDef, nextElement, theSubList.subList(1, theSubList.size()), theWantedClass); 286 retVal.addAll(foundValues); 287 } 288 } 289 return retVal; 290 } 291 292 public List<Object> getValues(IBaseResource theResource, String thePath) { 293 Class<Object> wantedClass = Object.class; 294 295 return getValues(theResource, thePath, wantedClass); 296 297 } 298 299 public <T> List<T> getValues(IBaseResource theResource, String thePath, Class<T> theWantedClass) { 300 RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource); 301 List<String> parts = parsePath(def, thePath); 302 return getValues(def, theResource, parts, theWantedClass); 303 } 304 305 private List<String> parsePath(BaseRuntimeElementCompositeDefinition<?> theElementDef, String thePath) { 306 List<String> parts = new ArrayList<>(); 307 308 int currentStart = 0; 309 boolean inSingleQuote = false; 310 for (int i = 0; i < thePath.length(); i++) { 311 switch (thePath.charAt(i)) { 312 case '\'': 313 inSingleQuote = !inSingleQuote; 314 break; 315 case '.': 316 if (!inSingleQuote) { 317 parts.add(thePath.substring(currentStart, i)); 318 currentStart = i + 1; 319 } 320 break; 321 } 322 } 323 324 parts.add(thePath.substring(currentStart)); 325 326 if (theElementDef instanceof RuntimeResourceDefinition) { 327 if (parts.size() > 0 && parts.get(0).equals(theElementDef.getName())) { 328 parts = parts.subList(1, parts.size()); 329 } 330 } 331 332 if (parts.size() < 1) { 333 throw new ConfigurationException("Invalid path: " + thePath); 334 } 335 return parts; 336 } 337 338 /** 339 * Returns <code>true</code> if <code>theSource</code> is in the compartment named <code>theCompartmentName</code> 340 * belonging to resource <code>theTarget</code> 341 * 342 * @param theCompartmentName The name of the compartment 343 * @param theSource The potential member of the compartment 344 * @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} 345 * @return <code>true</code> if <code>theSource</code> is in the compartment 346 * @throws IllegalArgumentException If theTarget does not contain both a resource type and ID 347 */ 348 public boolean isSourceInCompartmentForTarget(String theCompartmentName, IBaseResource theSource, IIdType theTarget) { 349 Validate.notBlank(theCompartmentName, "theCompartmentName must not be null or blank"); 350 Validate.notNull(theSource, "theSource must not be null"); 351 Validate.notNull(theTarget, "theTarget must not be null"); 352 Validate.notBlank(defaultString(theTarget.getResourceType()), "theTarget must have a populated resource type (theTarget.getResourceType() does not return a value)"); 353 Validate.notBlank(defaultString(theTarget.getIdPart()), "theTarget must have a populated ID (theTarget.getIdPart() does not return a value)"); 354 355 String wantRef = theTarget.toUnqualifiedVersionless().getValue(); 356 357 RuntimeResourceDefinition sourceDef = myContext.getResourceDefinition(theSource); 358 if (theSource.getIdElement().hasIdPart()) { 359 if (wantRef.equals(sourceDef.getName() + '/' + theSource.getIdElement().getIdPart())) { 360 return true; 361 } 362 } 363 364 List<RuntimeSearchParam> params = sourceDef.getSearchParamsForCompartmentName(theCompartmentName); 365 for (RuntimeSearchParam nextParam : params) { 366 for (String nextPath : nextParam.getPathsSplit()) { 367 for (IBaseReference nextValue : getValues(theSource, nextPath, IBaseReference.class)) { 368 String nextRef = nextValue.getReferenceElement().toUnqualifiedVersionless().getValue(); 369 370 /* 371 * If the reference isn't an explicit resource ID, but instead is just 372 * a resource object, we'll calculate its ID and treat the target 373 * as that. 374 */ 375 if (isBlank(nextRef) && nextValue.getResource() != null) { 376 IBaseResource nextTarget = nextValue.getResource(); 377 IIdType nextTargetId = nextTarget.getIdElement().toUnqualifiedVersionless(); 378 if (!nextTargetId.hasResourceType()) { 379 String resourceType = myContext.getResourceDefinition(nextTarget).getName(); 380 nextTargetId.setParts(null, resourceType, nextTargetId.getIdPart(), null); 381 } 382 nextRef = nextTargetId.getValue(); 383 } 384 385 if (wantRef.equals(nextRef)) { 386 return true; 387 } 388 } 389 } 390 } 391 392 return false; 393 } 394 395 private void visit(IBase theElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition<?> theDefinition, IModelVisitor2 theCallback, List<IBase> theContainingElementPath, 396 List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) { 397 if (theChildDefinition != null) { 398 theChildDefinitionPath.add(theChildDefinition); 399 } 400 theContainingElementPath.add(theElement); 401 theElementDefinitionPath.add(theDefinition); 402 403 theCallback.acceptElement(theElement, Collections.unmodifiableList(theContainingElementPath), Collections.unmodifiableList(theChildDefinitionPath), 404 Collections.unmodifiableList(theElementDefinitionPath)); 405 406 /* 407 * Visit undeclared extensions 408 */ 409 if (theElement instanceof ISupportsUndeclaredExtensions) { 410 ISupportsUndeclaredExtensions containingElement = (ISupportsUndeclaredExtensions) theElement; 411 for (ExtensionDt nextExt : containingElement.getUndeclaredExtensions()) { 412 theContainingElementPath.add(nextExt); 413 theCallback.acceptUndeclaredExtension(nextExt, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath); 414 theContainingElementPath.remove(theContainingElementPath.size() - 1); 415 } 416 } 417 418 /* 419 * Now visit the children of the given element 420 */ 421 switch (theDefinition.getChildType()) { 422 case ID_DATATYPE: 423 case PRIMITIVE_XHTML_HL7ORG: 424 case PRIMITIVE_XHTML: 425 case PRIMITIVE_DATATYPE: 426 // These are primitive types, so we don't need to visit their children 427 break; 428 case RESOURCE: 429 case RESOURCE_BLOCK: 430 case COMPOSITE_DATATYPE: { 431 BaseRuntimeElementCompositeDefinition<?> childDef = (BaseRuntimeElementCompositeDefinition<?>) theDefinition; 432 for (BaseRuntimeChildDefinition nextChild : childDef.getChildrenAndExtension()) { 433 List<? extends IBase> values = nextChild.getAccessor().getValues(theElement); 434 if (values != null) { 435 for (IBase nextValue : values) { 436 if (nextValue == null) { 437 continue; 438 } 439 if (nextValue.isEmpty()) { 440 continue; 441 } 442 BaseRuntimeElementDefinition<?> childElementDef; 443 childElementDef = nextChild.getChildElementDefinitionByDatatype(nextValue.getClass()); 444 445 if (childElementDef == null) { 446 StringBuilder b = new StringBuilder(); 447 b.append("Found value of type["); 448 b.append(nextValue.getClass().getSimpleName()); 449 b.append("] which is not valid for field["); 450 b.append(nextChild.getElementName()); 451 b.append("] in "); 452 b.append(childDef.getName()); 453 b.append(" - Valid types: "); 454 for (Iterator<String> iter = new TreeSet<String>(nextChild.getValidChildNames()).iterator(); iter.hasNext();) { 455 BaseRuntimeElementDefinition<?> childByName = nextChild.getChildByName(iter.next()); 456 b.append(childByName.getImplementingClass().getSimpleName()); 457 if (iter.hasNext()) { 458 b.append(", "); 459 } 460 } 461 throw new DataFormatException(b.toString()); 462 } 463 464 if (nextChild instanceof RuntimeChildDirectResource) { 465 // Don't descend into embedded resources 466 theContainingElementPath.add(nextValue); 467 theChildDefinitionPath.add(nextChild); 468 theElementDefinitionPath.add(myContext.getElementDefinition(nextValue.getClass())); 469 theCallback.acceptElement(nextValue, Collections.unmodifiableList(theContainingElementPath), Collections.unmodifiableList(theChildDefinitionPath), 470 Collections.unmodifiableList(theElementDefinitionPath)); 471 theChildDefinitionPath.remove(theChildDefinitionPath.size() - 1); 472 theContainingElementPath.remove(theContainingElementPath.size() - 1); 473 theElementDefinitionPath.remove(theElementDefinitionPath.size() - 1); 474 } else { 475 visit(nextValue, nextChild, childElementDef, theCallback, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath); 476 } 477 } 478 } 479 } 480 break; 481 } 482 case CONTAINED_RESOURCES: { 483 BaseContainedDt value = (BaseContainedDt) theElement; 484 for (IResource next : value.getContainedResources()) { 485 BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(next); 486 visit(next, null, def, theCallback, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath); 487 } 488 break; 489 } 490 case EXTENSION_DECLARED: 491 case UNDECL_EXT: { 492 throw new IllegalStateException("state should not happen: " + theDefinition.getChildType()); 493 } 494 case CONTAINED_RESOURCE_LIST: { 495 if (theElement != null) { 496 BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(theElement.getClass()); 497 visit(theElement, null, def, theCallback, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath); 498 } 499 break; 500 } 501 } 502 503 if (theChildDefinition != null) { 504 theChildDefinitionPath.remove(theChildDefinitionPath.size() - 1); 505 } 506 theContainingElementPath.remove(theContainingElementPath.size() - 1); 507 theElementDefinitionPath.remove(theElementDefinitionPath.size() - 1); 508 } 509 510 /** 511 * Visit all elements in a given resource 512 * 513 * <p> 514 * 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. 515 * {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource) 516 * </p> 517 * 518 * @param theResource 519 * The resource to visit 520 * @param theVisitor 521 * The visitor 522 */ 523 public void visit(IBaseResource theResource, IModelVisitor theVisitor) { 524 BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource); 525 visit(new IdentityHashMap<Object, Object>(), theResource, theResource, null, null, def, theVisitor); 526 } 527 528 /** 529 * Visit all elements in a given resource 530 * 531 * THIS ALTERNATE METHOD IS STILL EXPERIMENTAL 532 * 533 * <p> 534 * 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. 535 * {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource) 536 * </p> 537 * 538 * @param theResource 539 * The resource to visit 540 * @param theVisitor 541 * The visitor 542 */ 543 void visit(IBaseResource theResource, IModelVisitor2 theVisitor) { 544 BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource); 545 visit(theResource, null, def, theVisitor, new ArrayList<IBase>(), new ArrayList<BaseRuntimeChildDefinition>(), new ArrayList<BaseRuntimeElementDefinition<?>>()); 546 } 547 548 private void visit(IdentityHashMap<Object, Object> theStack, IBaseResource theResource, IBase theElement, List<String> thePathToElement, BaseRuntimeChildDefinition theChildDefinition, 549 BaseRuntimeElementDefinition<?> theDefinition, IModelVisitor theCallback) { 550 List<String> pathToElement = addNameToList(thePathToElement, theChildDefinition); 551 552 if (theStack.put(theElement, theElement) != null) { 553 return; 554 } 555 556 theCallback.acceptElement(theResource, theElement, pathToElement, theChildDefinition, theDefinition); 557 558 BaseRuntimeElementDefinition<?> def = theDefinition; 559 if (def.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST) { 560 def = myContext.getElementDefinition(theElement.getClass()); 561 } 562 563 if (theElement instanceof IBaseReference) { 564 IBaseResource target = ((IBaseReference)theElement).getResource(); 565 if (target != null) { 566 if (target.getIdElement().hasIdPart() == false || target.getIdElement().isLocal()) { 567 RuntimeResourceDefinition targetDef = myContext.getResourceDefinition(target); 568 visit(theStack, target, target, pathToElement, null, targetDef, theCallback); 569 } 570 } 571 } 572 573 switch (def.getChildType()) { 574 case ID_DATATYPE: 575 case PRIMITIVE_XHTML_HL7ORG: 576 case PRIMITIVE_XHTML: 577 case PRIMITIVE_DATATYPE: 578 // These are primitive types 579 break; 580 case RESOURCE: 581 case RESOURCE_BLOCK: 582 case COMPOSITE_DATATYPE: { 583 BaseRuntimeElementCompositeDefinition<?> childDef = (BaseRuntimeElementCompositeDefinition<?>) def; 584 for (BaseRuntimeChildDefinition nextChild : childDef.getChildrenAndExtension()) { 585 586 List<?> values = nextChild.getAccessor().getValues(theElement); 587 if (values != null) { 588 for (Object nextValueObject : values) { 589 IBase nextValue; 590 try { 591 nextValue = (IBase) nextValueObject; 592 } catch (ClassCastException e) { 593 String s = "Found instance of " + nextValueObject.getClass() + " - Did you set a field value to the incorrect type? Expected " + IBase.class.getName(); 594 throw new ClassCastException(s); 595 } 596 if (nextValue == null) { 597 continue; 598 } 599 if (nextValue.isEmpty()) { 600 continue; 601 } 602 BaseRuntimeElementDefinition<?> childElementDef; 603 childElementDef = nextChild.getChildElementDefinitionByDatatype(nextValue.getClass()); 604 605 if (childElementDef == null) { 606 childElementDef = myContext.getElementDefinition(nextValue.getClass()); 607 } 608 609 if (nextChild instanceof RuntimeChildDirectResource) { 610 // Don't descend into embedded resources 611 theCallback.acceptElement(theResource, nextValue, null, nextChild, childElementDef); 612 } else { 613 visit(theStack, theResource, nextValue, pathToElement, nextChild, childElementDef, theCallback); 614 } 615 } 616 } 617 } 618 break; 619 } 620 case CONTAINED_RESOURCES: { 621 BaseContainedDt value = (BaseContainedDt) theElement; 622 for (IResource next : value.getContainedResources()) { 623 def = myContext.getResourceDefinition(next); 624 visit(theStack, next, next, pathToElement, null, def, theCallback); 625 } 626 break; 627 } 628 case CONTAINED_RESOURCE_LIST: 629 case EXTENSION_DECLARED: 630 case UNDECL_EXT: { 631 throw new IllegalStateException("state should not happen: " + def.getChildType()); 632 } 633 } 634 635 theStack.remove(theElement); 636 637 } 638 639}