001package ca.uhn.fhir.parser; 002 003/* 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2018 University Health Network 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import ca.uhn.fhir.context.*; 024import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum; 025import ca.uhn.fhir.model.api.*; 026import ca.uhn.fhir.model.primitive.IdDt; 027import ca.uhn.fhir.rest.api.Constants; 028import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 029import ca.uhn.fhir.util.UrlUtil; 030import org.apache.commons.lang3.StringUtils; 031import org.apache.commons.lang3.Validate; 032import org.hl7.fhir.instance.model.api.*; 033 034import java.io.*; 035import java.lang.reflect.Modifier; 036import java.util.*; 037 038import static org.apache.commons.lang3.StringUtils.isBlank; 039import static org.apache.commons.lang3.StringUtils.isNotBlank; 040 041@SuppressWarnings("WeakerAccess") 042public abstract class BaseParser implements IParser { 043 044 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseParser.class); 045 046 private ContainedResources myContainedResources; 047 private boolean myEncodeElementsAppliesToChildResourcesOnly; 048 private FhirContext myContext; 049 private Set<String> myDontEncodeElements; 050 private boolean myDontEncodeElementsIncludesStars; 051 private Set<String> myEncodeElements; 052 private Set<String> myEncodeElementsAppliesToResourceTypes; 053 private boolean myEncodeElementsIncludesStars; 054 private IIdType myEncodeForceResourceId; 055 private IParserErrorHandler myErrorHandler; 056 private boolean myOmitResourceId; 057 private List<Class<? extends IBaseResource>> myPreferTypes; 058 private String myServerBaseUrl; 059 private Boolean myStripVersionsFromReferences; 060 private Boolean myOverrideResourceIdWithBundleEntryFullUrl; 061 private boolean mySummaryMode; 062 private boolean mySuppressNarratives; 063 private Set<String> myDontStripVersionsFromReferencesAtPaths; 064 065 /** 066 * Constructor 067 * 068 * @param theParserErrorHandler 069 */ 070 public BaseParser(FhirContext theContext, IParserErrorHandler theParserErrorHandler) { 071 myContext = theContext; 072 myErrorHandler = theParserErrorHandler; 073 } 074 075 protected Iterable<CompositeChildElement> compositeChildIterator(IBase theCompositeElement, final boolean theContainedResource, final boolean theSubResource, final CompositeChildElement theParent) { 076 077 BaseRuntimeElementCompositeDefinition<?> elementDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theCompositeElement.getClass()); 078 final List<BaseRuntimeChildDefinition> children = elementDef.getChildrenAndExtension(); 079 080 return new Iterable<BaseParser.CompositeChildElement>() { 081 @Override 082 public Iterator<CompositeChildElement> iterator() { 083 084 return new Iterator<CompositeChildElement>() { 085 private Iterator<? extends BaseRuntimeChildDefinition> myChildrenIter; 086 private Boolean myHasNext = null; 087 private CompositeChildElement myNext; 088 089 /** 090 * Constructor 091 */ { 092 myChildrenIter = children.iterator(); 093 } 094 095 @Override 096 public boolean hasNext() { 097 if (myHasNext != null) { 098 return myHasNext; 099 } 100 101 myNext = null; 102 do { 103 if (myChildrenIter.hasNext() == false) { 104 myHasNext = Boolean.FALSE; 105 return false; 106 } 107 108 myNext = new CompositeChildElement(theParent, myChildrenIter.next(), theSubResource); 109 110 /* 111 * There are lots of reasons we might skip encoding a particular child 112 */ 113 if (myNext.getDef().getElementName().equals("id")) { 114 myNext = null; 115 } else if (!myNext.shouldBeEncoded()) { 116 myNext = null; 117 } else if (isSummaryMode() && !myNext.getDef().isSummary()) { 118 myNext = null; 119 } else if (myNext.getDef() instanceof RuntimeChildNarrativeDefinition) { 120 if (isSuppressNarratives() || isSummaryMode()) { 121 myNext = null; 122 } else if (theContainedResource) { 123 myNext = null; 124 } 125 } else if (myNext.getDef() instanceof RuntimeChildContainedResources) { 126 if (theContainedResource) { 127 myNext = null; 128 } 129 } 130 131 } while (myNext == null); 132 133 myHasNext = true; 134 return true; 135 } 136 137 @Override 138 public CompositeChildElement next() { 139 if (myHasNext == null) { 140 if (!hasNext()) { 141 throw new IllegalStateException(); 142 } 143 } 144 CompositeChildElement retVal = myNext; 145 myNext = null; 146 myHasNext = null; 147 return retVal; 148 } 149 150 @Override 151 public void remove() { 152 throw new UnsupportedOperationException(); 153 } 154 }; 155 } 156 }; 157 } 158 159 private void containResourcesForEncoding(ContainedResources theContained, IBaseResource theResource, IBaseResource theTarget) { 160 161 if (theTarget instanceof IResource) { 162 List<? extends IResource> containedResources = ((IResource) theTarget).getContained().getContainedResources(); 163 for (IResource next : containedResources) { 164 String nextId = next.getId().getValue(); 165 if (StringUtils.isNotBlank(nextId)) { 166 if (!nextId.startsWith("#")) { 167 nextId = '#' + nextId; 168 } 169 theContained.getExistingIdToContainedResource().put(nextId, next); 170 } 171 } 172 } else if (theTarget instanceof IDomainResource) { 173 List<? extends IAnyResource> containedResources = ((IDomainResource) theTarget).getContained(); 174 for (IAnyResource next : containedResources) { 175 String nextId = next.getIdElement().getValue(); 176 if (StringUtils.isNotBlank(nextId)) { 177 if (!nextId.startsWith("#")) { 178 nextId = '#' + nextId; 179 } 180 theContained.getExistingIdToContainedResource().put(nextId, next); 181 } 182 } 183 } else { 184 // no resources to contain 185 } 186 187 List<IBaseReference> allReferences = myContext.newTerser().getAllPopulatedChildElementsOfType(theResource, IBaseReference.class); 188 for (IBaseReference next : allReferences) { 189 IBaseResource resource = next.getResource(); 190 if (resource == null && next.getReferenceElement().isLocal()) { 191 if (theContained.hasExistingIdToContainedResource()) { 192 IBaseResource potentialTarget = theContained.getExistingIdToContainedResource().remove(next.getReferenceElement().getValue()); 193 if (potentialTarget != null) { 194 theContained.addContained(next.getReferenceElement(), potentialTarget); 195 containResourcesForEncoding(theContained, potentialTarget, theTarget); 196 } 197 } 198 } 199 } 200 201 for (IBaseReference next : allReferences) { 202 IBaseResource resource = next.getResource(); 203 if (resource != null) { 204 if (resource.getIdElement().isEmpty() || resource.getIdElement().isLocal()) { 205 if (theContained.getResourceId(resource) != null) { 206 // Prevent infinite recursion if there are circular loops in the contained resources 207 continue; 208 } 209 theContained.addContained(resource); 210 if (resource.getIdElement().isLocal() && theContained.hasExistingIdToContainedResource()) { 211 theContained.getExistingIdToContainedResource().remove(resource.getIdElement().getValue()); 212 } 213 } else { 214 continue; 215 } 216 217 containResourcesForEncoding(theContained, resource, theTarget); 218 } 219 220 } 221 222 } 223 224 protected void containResourcesForEncoding(IBaseResource theResource) { 225 ContainedResources contained = new ContainedResources(); 226 containResourcesForEncoding(contained, theResource, theResource); 227 contained.assignIdsToContainedResources(); 228 myContainedResources = contained; 229 230 } 231 232 private String determineReferenceText(IBaseReference theRef, CompositeChildElement theCompositeChildElement) { 233 IIdType ref = theRef.getReferenceElement(); 234 if (isBlank(ref.getIdPart())) { 235 String reference = ref.getValue(); 236 if (theRef.getResource() != null) { 237 IIdType containedId = getContainedResources().getResourceId(theRef.getResource()); 238 if (containedId != null && !containedId.isEmpty()) { 239 if (containedId.isLocal()) { 240 reference = containedId.getValue(); 241 } else { 242 reference = "#" + containedId.getValue(); 243 } 244 } else { 245 IIdType refId = theRef.getResource().getIdElement(); 246 if (refId != null) { 247 if (refId.hasIdPart()) { 248 if (refId.getValue().startsWith("urn:")) { 249 reference = refId.getValue(); 250 } else { 251 if (!refId.hasResourceType()) { 252 refId = refId.withResourceType(myContext.getResourceDefinition(theRef.getResource()).getName()); 253 } 254 if (isStripVersionsFromReferences(theCompositeChildElement)) { 255 reference = refId.toVersionless().getValue(); 256 } else { 257 reference = refId.getValue(); 258 } 259 } 260 } 261 } 262 } 263 } 264 return reference; 265 } 266 if (!ref.hasResourceType() && !ref.isLocal() && theRef.getResource() != null) { 267 ref = ref.withResourceType(myContext.getResourceDefinition(theRef.getResource()).getName()); 268 } 269 if (isNotBlank(myServerBaseUrl) && StringUtils.equals(myServerBaseUrl, ref.getBaseUrl())) { 270 if (isStripVersionsFromReferences(theCompositeChildElement)) { 271 return ref.toUnqualifiedVersionless().getValue(); 272 } 273 return ref.toUnqualified().getValue(); 274 } 275 if (isStripVersionsFromReferences(theCompositeChildElement)) { 276 return ref.toVersionless().getValue(); 277 } 278 return ref.getValue(); 279 } 280 281 protected abstract void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws IOException, DataFormatException; 282 283 protected abstract <T extends IBaseResource> T doParseResource(Class<T> theResourceType, Reader theReader) throws DataFormatException; 284 285 @Override 286 public String encodeResourceToString(IBaseResource theResource) throws DataFormatException { 287 Writer stringWriter = new StringWriter(); 288 try { 289 encodeResourceToWriter(theResource, stringWriter); 290 } catch (IOException e) { 291 throw new Error("Encountered IOException during write to string - This should not happen!"); 292 } 293 return stringWriter.toString(); 294 } 295 296 @Override 297 public final void encodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws IOException, DataFormatException { 298 Validate.notNull(theResource, "theResource can not be null"); 299 Validate.notNull(theWriter, "theWriter can not be null"); 300 301 if (theResource.getStructureFhirVersionEnum() != myContext.getVersion().getVersion()) { 302 throw new IllegalArgumentException( 303 "This parser is for FHIR version " + myContext.getVersion().getVersion() + " - Can not encode a structure for version " + theResource.getStructureFhirVersionEnum()); 304 } 305 306 doEncodeResourceToWriter(theResource, theWriter); 307 } 308 309 private void filterCodingsWithNoCodeOrSystem(List<? extends IBaseCoding> tagList) { 310 for (int i = 0; i < tagList.size(); i++) { 311 if (isBlank(tagList.get(i).getCode()) && isBlank(tagList.get(i).getSystem())) { 312 tagList.remove(i); 313 i--; 314 } 315 } 316 } 317 318 protected IIdType fixContainedResourceId(String theValue) { 319 IIdType retVal = (IIdType) myContext.getElementDefinition("id").newInstance(); 320 if (StringUtils.isNotBlank(theValue) && theValue.charAt(0) == '#') { 321 retVal.setValue(theValue.substring(1)); 322 } else { 323 retVal.setValue(theValue); 324 } 325 return retVal; 326 } 327 328 @SuppressWarnings("unchecked") 329 ChildNameAndDef getChildNameAndDef(BaseRuntimeChildDefinition theChild, IBase theValue) { 330 Class<? extends IBase> type = theValue.getClass(); 331 String childName = theChild.getChildNameByDatatype(type); 332 BaseRuntimeElementDefinition<?> childDef = theChild.getChildElementDefinitionByDatatype(type); 333 if (childDef == null) { 334 // if (theValue instanceof IBaseExtension) { 335 // return null; 336 // } 337 338 /* 339 * For RI structures Enumeration class, this replaces the child def 340 * with the "code" one. This is messy, and presumably there is a better 341 * way.. 342 */ 343 BaseRuntimeElementDefinition<?> elementDef = myContext.getElementDefinition(type); 344 if (elementDef.getName().equals("code")) { 345 Class<? extends IBase> type2 = myContext.getElementDefinition("code").getImplementingClass(); 346 childDef = theChild.getChildElementDefinitionByDatatype(type2); 347 childName = theChild.getChildNameByDatatype(type2); 348 } 349 350 // See possibly the user has extended a built-in type without 351 // declaring it anywhere, as in XmlParserDstu3Test#testEncodeUndeclaredBlock 352 if (childDef == null) { 353 Class<?> nextSuperType = theValue.getClass(); 354 while (IBase.class.isAssignableFrom(nextSuperType) && childDef == null) { 355 if (Modifier.isAbstract(nextSuperType.getModifiers()) == false) { 356 BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition((Class<? extends IBase>) nextSuperType); 357 Class<?> nextChildType = def.getImplementingClass(); 358 childDef = theChild.getChildElementDefinitionByDatatype((Class<? extends IBase>) nextChildType); 359 childName = theChild.getChildNameByDatatype((Class<? extends IBase>) nextChildType); 360 } 361 nextSuperType = nextSuperType.getSuperclass(); 362 } 363 } 364 365 if (childDef == null) { 366 throwExceptionForUnknownChildType(theChild, type); 367 } 368 } 369 370 return new ChildNameAndDef(childName, childDef); 371 } 372 373 protected String getCompositeElementId(IBase theElement) { 374 String elementId = null; 375 if (!(theElement instanceof IBaseResource)) { 376 if (theElement instanceof IBaseElement) { 377 elementId = ((IBaseElement) theElement).getId(); 378 } else if (theElement instanceof IIdentifiableElement) { 379 elementId = ((IIdentifiableElement) theElement).getElementSpecificId(); 380 } 381 } 382 return elementId; 383 } 384 385 ContainedResources getContainedResources() { 386 return myContainedResources; 387 } 388 389 @Override 390 public Set<String> getDontStripVersionsFromReferencesAtPaths() { 391 return myDontStripVersionsFromReferencesAtPaths; 392 } 393 394 /** 395 * See {@link #setEncodeElements(Set)} 396 */ 397 @Override 398 public Set<String> getEncodeElements() { 399 return myEncodeElements; 400 } 401 402 @Override 403 public void setEncodeElements(Set<String> theEncodeElements) { 404 myEncodeElementsIncludesStars = false; 405 if (theEncodeElements == null || theEncodeElements.isEmpty()) { 406 myEncodeElements = null; 407 } else { 408 myEncodeElements = theEncodeElements; 409 for (String next : theEncodeElements) { 410 if (next.startsWith("*.")) { 411 myEncodeElementsIncludesStars = true; 412 } 413 } 414 } 415 } 416 417 /** 418 * See {@link #setEncodeElementsAppliesToResourceTypes(Set)} 419 */ 420 @Override 421 public Set<String> getEncodeElementsAppliesToResourceTypes() { 422 return myEncodeElementsAppliesToResourceTypes; 423 } 424 425 @Override 426 public void setEncodeElementsAppliesToResourceTypes(Set<String> theEncodeElementsAppliesToResourceTypes) { 427 if (theEncodeElementsAppliesToResourceTypes == null || theEncodeElementsAppliesToResourceTypes.isEmpty()) { 428 myEncodeElementsAppliesToResourceTypes = null; 429 } else { 430 myEncodeElementsAppliesToResourceTypes = theEncodeElementsAppliesToResourceTypes; 431 } 432 } 433 434 @Override 435 public IIdType getEncodeForceResourceId() { 436 return myEncodeForceResourceId; 437 } 438 439 @Override 440 public BaseParser setEncodeForceResourceId(IIdType theEncodeForceResourceId) { 441 myEncodeForceResourceId = theEncodeForceResourceId; 442 return this; 443 } 444 445 protected IParserErrorHandler getErrorHandler() { 446 return myErrorHandler; 447 } 448 449 protected List<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> getExtensionMetadataKeys(IResource resource) { 450 List<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> extensionMetadataKeys = new ArrayList<Map.Entry<ResourceMetadataKeyEnum<?>, Object>>(); 451 for (Map.Entry<ResourceMetadataKeyEnum<?>, Object> entry : resource.getResourceMetadata().entrySet()) { 452 if (entry.getKey() instanceof ResourceMetadataKeyEnum.ExtensionResourceMetadataKey) { 453 extensionMetadataKeys.add(entry); 454 } 455 } 456 457 return extensionMetadataKeys; 458 } 459 460 protected String getExtensionUrl(final String extensionUrl) { 461 String url = extensionUrl; 462 if (StringUtils.isNotBlank(extensionUrl) && StringUtils.isNotBlank(myServerBaseUrl)) { 463 url = !UrlUtil.isValid(extensionUrl) && extensionUrl.startsWith("/") ? myServerBaseUrl + extensionUrl : extensionUrl; 464 } 465 return url; 466 } 467 468 protected TagList getMetaTagsForEncoding(IResource theIResource) { 469 TagList tags = ResourceMetadataKeyEnum.TAG_LIST.get(theIResource); 470 if (shouldAddSubsettedTag()) { 471 tags = new TagList(tags); 472 tags.add(new Tag(getSubsettedCodeSystem(), Constants.TAG_SUBSETTED_CODE, subsetDescription())); 473 } 474 475 return tags; 476 } 477 478 @Override 479 public Boolean getOverrideResourceIdWithBundleEntryFullUrl() { 480 return myOverrideResourceIdWithBundleEntryFullUrl; 481 } 482 483 @Override 484 public List<Class<? extends IBaseResource>> getPreferTypes() { 485 return myPreferTypes; 486 } 487 488 @Override 489 public void setPreferTypes(List<Class<? extends IBaseResource>> thePreferTypes) { 490 if (thePreferTypes != null) { 491 ArrayList<Class<? extends IBaseResource>> types = new ArrayList<>(); 492 for (Class<? extends IBaseResource> next : thePreferTypes) { 493 if (Modifier.isAbstract(next.getModifiers()) == false) { 494 types.add(next); 495 } 496 } 497 myPreferTypes = Collections.unmodifiableList(types); 498 } else { 499 myPreferTypes = thePreferTypes; 500 } 501 } 502 503 @SuppressWarnings("deprecation") 504 protected <T extends IPrimitiveType<String>> List<T> getProfileTagsForEncoding(IBaseResource theResource, List<T> theProfiles) { 505 switch (myContext.getAddProfileTagWhenEncoding()) { 506 case NEVER: 507 return theProfiles; 508 case ONLY_FOR_CUSTOM: 509 RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource); 510 if (resDef.isStandardType()) { 511 return theProfiles; 512 } 513 break; 514 case ALWAYS: 515 break; 516 } 517 518 RuntimeResourceDefinition nextDef = myContext.getResourceDefinition(theResource); 519 String profile = nextDef.getResourceProfile(myServerBaseUrl); 520 if (isNotBlank(profile)) { 521 for (T next : theProfiles) { 522 if (profile.equals(next.getValue())) { 523 return theProfiles; 524 } 525 } 526 527 List<T> newList = new ArrayList<>(theProfiles); 528 529 BaseRuntimeElementDefinition<?> idElement = myContext.getElementDefinition("id"); 530 @SuppressWarnings("unchecked") 531 T newId = (T) idElement.newInstance(); 532 newId.setValue(profile); 533 534 newList.add(newId); 535 return newList; 536 } 537 538 return theProfiles; 539 } 540 541 protected String getServerBaseUrl() { 542 return myServerBaseUrl; 543 } 544 545 @Override 546 public Boolean getStripVersionsFromReferences() { 547 return myStripVersionsFromReferences; 548 } 549 550 /** 551 * If set to <code>true</code> (default is <code>false</code>), narratives will not be included in the encoded 552 * values. 553 * 554 * @deprecated Use {@link #isSuppressNarratives()} 555 */ 556 @Deprecated 557 public boolean getSuppressNarratives() { 558 return mySuppressNarratives; 559 } 560 561 protected boolean isChildContained(BaseRuntimeElementDefinition<?> childDef, boolean theIncludedResource) { 562 return (childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCES || childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST) && getContainedResources().isEmpty() == false 563 && theIncludedResource == false; 564 } 565 566 @Override 567 public boolean isEncodeElementsAppliesToChildResourcesOnly() { 568 return myEncodeElementsAppliesToChildResourcesOnly; 569 } 570 571 @Override 572 public void setEncodeElementsAppliesToChildResourcesOnly(boolean theEncodeElementsAppliesToChildResourcesOnly) { 573 myEncodeElementsAppliesToChildResourcesOnly = theEncodeElementsAppliesToChildResourcesOnly; 574 } 575 576 @Override 577 public boolean isOmitResourceId() { 578 return myOmitResourceId; 579 } 580 581 private boolean isOverrideResourceIdWithBundleEntryFullUrl() { 582 Boolean overrideResourceIdWithBundleEntryFullUrl = myOverrideResourceIdWithBundleEntryFullUrl; 583 if (overrideResourceIdWithBundleEntryFullUrl != null) { 584 return overrideResourceIdWithBundleEntryFullUrl; 585 } 586 587 return myContext.getParserOptions().isOverrideResourceIdWithBundleEntryFullUrl(); 588 } 589 590 private boolean isStripVersionsFromReferences(CompositeChildElement theCompositeChildElement) { 591 Boolean stripVersionsFromReferences = myStripVersionsFromReferences; 592 if (stripVersionsFromReferences != null) { 593 return stripVersionsFromReferences; 594 } 595 596 if (myContext.getParserOptions().isStripVersionsFromReferences() == false) { 597 return false; 598 } 599 600 Set<String> dontStripVersionsFromReferencesAtPaths = myDontStripVersionsFromReferencesAtPaths; 601 if (dontStripVersionsFromReferencesAtPaths != null) { 602 if (dontStripVersionsFromReferencesAtPaths.isEmpty() == false && theCompositeChildElement.anyPathMatches(dontStripVersionsFromReferencesAtPaths)) { 603 return false; 604 } 605 } 606 607 dontStripVersionsFromReferencesAtPaths = myContext.getParserOptions().getDontStripVersionsFromReferencesAtPaths(); 608 return dontStripVersionsFromReferencesAtPaths.isEmpty() != false || !theCompositeChildElement.anyPathMatches(dontStripVersionsFromReferencesAtPaths); 609 } 610 611 @Override 612 public boolean isSummaryMode() { 613 return mySummaryMode; 614 } 615 616 /** 617 * If set to <code>true</code> (default is <code>false</code>), narratives will not be included in the encoded 618 * values. 619 * 620 * @since 1.2 621 */ 622 public boolean isSuppressNarratives() { 623 return mySuppressNarratives; 624 } 625 626 @Override 627 public <T extends IBaseResource> T parseResource(Class<T> theResourceType, Reader theReader) throws DataFormatException { 628 629 /* 630 * We do this so that the context can verify that the structure is for 631 * the correct FHIR version 632 */ 633 if (theResourceType != null) { 634 myContext.getResourceDefinition(theResourceType); 635 } 636 637 // Actually do the parse 638 T retVal = doParseResource(theResourceType, theReader); 639 640 RuntimeResourceDefinition def = myContext.getResourceDefinition(retVal); 641 if ("Bundle".equals(def.getName())) { 642 643 BaseRuntimeChildDefinition entryChild = def.getChildByName("entry"); 644 BaseRuntimeElementCompositeDefinition<?> entryDef = (BaseRuntimeElementCompositeDefinition<?>) entryChild.getChildByName("entry"); 645 List<IBase> entries = entryChild.getAccessor().getValues(retVal); 646 if (entries != null) { 647 for (IBase nextEntry : entries) { 648 649 /** 650 * If Bundle.entry.fullUrl is populated, set the resource ID to that 651 */ 652 // TODO: should emit a warning and maybe notify the error handler if the resource ID doesn't match the 653 // fullUrl idPart 654 BaseRuntimeChildDefinition fullUrlChild = entryDef.getChildByName("fullUrl"); 655 if (fullUrlChild == null) { 656 continue; // TODO: remove this once the data model in tinder plugin catches up to 1.2 657 } 658 if (isOverrideResourceIdWithBundleEntryFullUrl()) { 659 List<IBase> fullUrl = fullUrlChild.getAccessor().getValues(nextEntry); 660 if (fullUrl != null && !fullUrl.isEmpty()) { 661 IPrimitiveType<?> value = (IPrimitiveType<?>) fullUrl.get(0); 662 if (value.isEmpty() == false) { 663 List<IBase> entryResources = entryDef.getChildByName("resource").getAccessor().getValues(nextEntry); 664 if (entryResources != null && entryResources.size() > 0) { 665 IBaseResource res = (IBaseResource) entryResources.get(0); 666 String versionId = res.getIdElement().getVersionIdPart(); 667 res.setId(value.getValueAsString()); 668 if (isNotBlank(versionId) && res.getIdElement().hasVersionIdPart() == false) { 669 res.setId(res.getIdElement().withVersion(versionId)); 670 } 671 } 672 } 673 } 674 } 675 } 676 } 677 678 } 679 680 return retVal; 681 } 682 683 @SuppressWarnings("cast") 684 @Override 685 public <T extends IBaseResource> T parseResource(Class<T> theResourceType, String theMessageString) { 686 StringReader reader = new StringReader(theMessageString); 687 return parseResource(theResourceType, reader); 688 } 689 690 @Override 691 public IBaseResource parseResource(Reader theReader) throws ConfigurationException, DataFormatException { 692 return parseResource(null, theReader); 693 } 694 695 @Override 696 public IBaseResource parseResource(String theMessageString) throws ConfigurationException, DataFormatException { 697 return parseResource(null, theMessageString); 698 } 699 700 protected List<? extends IBase> preProcessValues(BaseRuntimeChildDefinition theMetaChildUncast, IBaseResource theResource, List<? extends IBase> theValues, 701 CompositeChildElement theCompositeChildElement) { 702 if (myContext.getVersion().getVersion().isRi()) { 703 704 /* 705 * If we're encoding the meta tag, we do some massaging of the meta values before 706 * encoding. But if there is no meta element at all, we create one since we're possibly going to be 707 * adding things to it 708 */ 709 if (theValues.isEmpty() && theMetaChildUncast.getElementName().equals("meta")) { 710 BaseRuntimeElementDefinition<?> metaChild = theMetaChildUncast.getChildByName("meta"); 711 if (IBaseMetaType.class.isAssignableFrom(metaChild.getImplementingClass())) { 712 IBaseMetaType newType = (IBaseMetaType) metaChild.newInstance(); 713 theValues = Collections.singletonList(newType); 714 } 715 } 716 717 if (theValues.size() == 1 && theValues.get(0) instanceof IBaseMetaType) { 718 719 IBaseMetaType metaValue = (IBaseMetaType) theValues.get(0); 720 try { 721 metaValue = (IBaseMetaType) metaValue.getClass().getMethod("copy").invoke(metaValue); 722 } catch (Exception e) { 723 throw new InternalErrorException("Failed to duplicate meta", e); 724 } 725 726 if (isBlank(metaValue.getVersionId())) { 727 if (theResource.getIdElement().hasVersionIdPart()) { 728 metaValue.setVersionId(theResource.getIdElement().getVersionIdPart()); 729 } 730 } 731 732 filterCodingsWithNoCodeOrSystem(metaValue.getTag()); 733 filterCodingsWithNoCodeOrSystem(metaValue.getSecurity()); 734 735 List<? extends IPrimitiveType<String>> newProfileList = getProfileTagsForEncoding(theResource, metaValue.getProfile()); 736 List<? extends IPrimitiveType<String>> oldProfileList = metaValue.getProfile(); 737 if (oldProfileList != newProfileList) { 738 oldProfileList.clear(); 739 for (IPrimitiveType<String> next : newProfileList) { 740 if (isNotBlank(next.getValue())) { 741 metaValue.addProfile(next.getValue()); 742 } 743 } 744 } 745 746 if (shouldAddSubsettedTag()) { 747 IBaseCoding coding = metaValue.addTag(); 748 coding.setCode(Constants.TAG_SUBSETTED_CODE); 749 coding.setSystem(getSubsettedCodeSystem()); 750 coding.setDisplay(subsetDescription()); 751 } 752 753 return Collections.singletonList(metaValue); 754 } 755 } 756 757 @SuppressWarnings("unchecked") 758 List<IBase> retVal = (List<IBase>) theValues; 759 760 for (int i = 0; i < retVal.size(); i++) { 761 IBase next = retVal.get(i); 762 763 /* 764 * If we have automatically contained any resources via 765 * their references, this ensures that we output the new 766 * local reference 767 */ 768 if (next instanceof IBaseReference) { 769 IBaseReference nextRef = (IBaseReference) next; 770 String refText = determineReferenceText(nextRef, theCompositeChildElement); 771 if (!StringUtils.equals(refText, nextRef.getReferenceElement().getValue())) { 772 773 if (retVal == theValues) { 774 retVal = new ArrayList<>(theValues); 775 } 776 IBaseReference newRef = (IBaseReference) myContext.getElementDefinition(nextRef.getClass()).newInstance(); 777 myContext.newTerser().cloneInto(nextRef, newRef, true); 778 newRef.setReference(refText); 779 retVal.set(i, newRef); 780 781 } 782 } 783 } 784 785 return retVal; 786 } 787 788 private String getSubsettedCodeSystem() { 789 if (myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) { 790 return Constants.TAG_SUBSETTED_SYSTEM_R4; 791 } else { 792 return Constants.TAG_SUBSETTED_SYSTEM_DSTU3; 793 } 794 } 795 796 @Override 797 public void setDontEncodeElements(Set<String> theDontEncodeElements) { 798 myDontEncodeElementsIncludesStars = false; 799 if (theDontEncodeElements == null || theDontEncodeElements.isEmpty()) { 800 myDontEncodeElements = null; 801 } else { 802 myDontEncodeElements = theDontEncodeElements; 803 for (String next : theDontEncodeElements) { 804 if (next.startsWith("*.")) { 805 myDontEncodeElementsIncludesStars = true; 806 } 807 } 808 } 809 } 810 811 @Override 812 public IParser setDontStripVersionsFromReferencesAtPaths(String... thePaths) { 813 if (thePaths == null) { 814 setDontStripVersionsFromReferencesAtPaths((List<String>) null); 815 } else { 816 setDontStripVersionsFromReferencesAtPaths(Arrays.asList(thePaths)); 817 } 818 return this; 819 } 820 821 @SuppressWarnings("unchecked") 822 @Override 823 public IParser setDontStripVersionsFromReferencesAtPaths(Collection<String> thePaths) { 824 if (thePaths == null) { 825 myDontStripVersionsFromReferencesAtPaths = Collections.emptySet(); 826 } else if (thePaths instanceof HashSet) { 827 myDontStripVersionsFromReferencesAtPaths = (Set<String>) ((HashSet<String>) thePaths).clone(); 828 } else { 829 myDontStripVersionsFromReferencesAtPaths = new HashSet<>(thePaths); 830 } 831 return this; 832 } 833 834 @Override 835 public IParser setOmitResourceId(boolean theOmitResourceId) { 836 myOmitResourceId = theOmitResourceId; 837 return this; 838 } 839 840 @Override 841 public IParser setOverrideResourceIdWithBundleEntryFullUrl(Boolean theOverrideResourceIdWithBundleEntryFullUrl) { 842 myOverrideResourceIdWithBundleEntryFullUrl = theOverrideResourceIdWithBundleEntryFullUrl; 843 return this; 844 } 845 846 @Override 847 public IParser setParserErrorHandler(IParserErrorHandler theErrorHandler) { 848 Validate.notNull(theErrorHandler, "theErrorHandler must not be null"); 849 myErrorHandler = theErrorHandler; 850 return this; 851 } 852 853 @Override 854 public IParser setServerBaseUrl(String theUrl) { 855 myServerBaseUrl = isNotBlank(theUrl) ? theUrl : null; 856 return this; 857 } 858 859 @Override 860 public IParser setStripVersionsFromReferences(Boolean theStripVersionsFromReferences) { 861 myStripVersionsFromReferences = theStripVersionsFromReferences; 862 return this; 863 } 864 865 @Override 866 public IParser setSummaryMode(boolean theSummaryMode) { 867 mySummaryMode = theSummaryMode; 868 return this; 869 } 870 871 @Override 872 public IParser setSuppressNarratives(boolean theSuppressNarratives) { 873 mySuppressNarratives = theSuppressNarratives; 874 return this; 875 } 876 877 protected boolean shouldAddSubsettedTag() { 878 return isSummaryMode() || isSuppressNarratives() || getEncodeElements() != null; 879 } 880 881 protected boolean shouldEncodeResourceId(IBaseResource theResource, boolean theSubResource) { 882 boolean retVal = true; 883 if (isOmitResourceId()) { 884 retVal = false; 885 } else { 886 if (myDontEncodeElements != null) { 887 String resourceName = myContext.getResourceDefinition(theResource).getName(); 888 if (myDontEncodeElements.contains(resourceName + ".id")) { 889 retVal = false; 890 } else if (myDontEncodeElements.contains("*.id")) { 891 retVal = false; 892 } else if (theSubResource == false && myDontEncodeElements.contains("id")) { 893 retVal = false; 894 } 895 } 896 } 897 return retVal; 898 } 899 900 /** 901 * Used for DSTU2 only 902 */ 903 protected boolean shouldEncodeResourceMeta(IResource theResource) { 904 if (myDontEncodeElements != null) { 905 String resourceName = myContext.getResourceDefinition(theResource).getName(); 906 if (myDontEncodeElements.contains(resourceName + ".meta")) { 907 return false; 908 } else return !myDontEncodeElements.contains("*.meta"); 909 } 910 return true; 911 } 912 913 private String subsetDescription() { 914 return "Resource encoded in summary mode"; 915 } 916 917 protected void throwExceptionForUnknownChildType(BaseRuntimeChildDefinition nextChild, Class<? extends IBase> theType) { 918 if (nextChild instanceof BaseRuntimeDeclaredChildDefinition) { 919 StringBuilder b = new StringBuilder(); 920 b.append(nextChild.getElementName()); 921 b.append(" has type "); 922 b.append(theType.getName()); 923 b.append(" but this is not a valid type for this element"); 924 if (nextChild instanceof RuntimeChildChoiceDefinition) { 925 RuntimeChildChoiceDefinition choice = (RuntimeChildChoiceDefinition) nextChild; 926 b.append(" - Expected one of: " + choice.getValidChildTypes()); 927 } 928 throw new DataFormatException(b.toString()); 929 } 930 throw new DataFormatException(nextChild + " has no child of type " + theType); 931 } 932 933 class ChildNameAndDef { 934 935 private final BaseRuntimeElementDefinition<?> myChildDef; 936 private final String myChildName; 937 938 public ChildNameAndDef(String theChildName, BaseRuntimeElementDefinition<?> theChildDef) { 939 myChildName = theChildName; 940 myChildDef = theChildDef; 941 } 942 943 public BaseRuntimeElementDefinition<?> getChildDef() { 944 return myChildDef; 945 } 946 947 public String getChildName() { 948 return myChildName; 949 } 950 951 } 952 953 protected class CompositeChildElement { 954 private final BaseRuntimeChildDefinition myDef; 955 private final CompositeChildElement myParent; 956 private final RuntimeResourceDefinition myResDef; 957 private final boolean mySubResource; 958 959 public CompositeChildElement(CompositeChildElement theParent, BaseRuntimeChildDefinition theDef, boolean theSubResource) { 960 myDef = theDef; 961 myParent = theParent; 962 myResDef = null; 963 mySubResource = theSubResource; 964 965 if (ourLog.isTraceEnabled()) { 966 if (theParent != null) { 967 StringBuilder path = theParent.buildPath(); 968 if (path != null) { 969 path.append('.'); 970 path.append(myDef.getElementName()); 971 ourLog.trace(" * Next path: {}", path.toString()); 972 } 973 } 974 } 975 976 } 977 978 public CompositeChildElement(RuntimeResourceDefinition theResDef, boolean theSubResource) { 979 myResDef = theResDef; 980 myDef = null; 981 myParent = null; 982 mySubResource = theSubResource; 983 } 984 985 private void addParent(CompositeChildElement theParent, StringBuilder theB) { 986 if (theParent != null) { 987 if (theParent.myResDef != null) { 988 theB.append(theParent.myResDef.getName()); 989 return; 990 } 991 992 if (theParent.myParent != null) { 993 addParent(theParent.myParent, theB); 994 } 995 996 if (theParent.myDef != null) { 997 if (theB.length() > 0) { 998 theB.append('.'); 999 } 1000 theB.append(theParent.myDef.getElementName()); 1001 } 1002 } 1003 } 1004 1005 public boolean anyPathMatches(Set<String> thePaths) { 1006 StringBuilder b = new StringBuilder(); 1007 addParent(this, b); 1008 1009 String path = b.toString(); 1010 return thePaths.contains(path); 1011 } 1012 1013 private StringBuilder buildPath() { 1014 if (myResDef != null) { 1015 StringBuilder b = new StringBuilder(); 1016 b.append(myResDef.getName()); 1017 return b; 1018 } else if (myParent != null) { 1019 StringBuilder b = myParent.buildPath(); 1020 if (b != null && myDef != null) { 1021 b.append('.'); 1022 b.append(myDef.getElementName()); 1023 } 1024 return b; 1025 } else { 1026 return null; 1027 } 1028 } 1029 1030 private boolean checkIfParentShouldBeEncodedAndBuildPath(StringBuilder thePathBuilder, boolean theStarPass) { 1031 Set<String> encodeElements = myEncodeElements; 1032 if (encodeElements != null && encodeElements.isEmpty() == false) { 1033 if (isEncodeElementsAppliesToChildResourcesOnly() && !mySubResource) { 1034 encodeElements = null; 1035 } 1036 } 1037 return checkIfPathMatchesForEncoding(thePathBuilder, theStarPass, myEncodeElementsAppliesToResourceTypes, encodeElements, true); 1038 } 1039 1040 private boolean checkIfParentShouldNotBeEncodedAndBuildPath(StringBuilder thePathBuilder, boolean theStarPass) { 1041 return checkIfPathMatchesForEncoding(thePathBuilder, theStarPass, null, myDontEncodeElements, false); 1042 } 1043 1044 private boolean checkIfPathMatchesForEncoding(StringBuilder thePathBuilder, boolean theStarPass, Set<String> theResourceTypes, Set<String> theElements, boolean theCheckingForWhitelist) { 1045 if (myResDef != null) { 1046 if (theResourceTypes != null) { 1047 if (!theResourceTypes.contains(myResDef.getName())) { 1048 return true; 1049 } 1050 } 1051 if (theStarPass) { 1052 thePathBuilder.append('*'); 1053 } else { 1054 thePathBuilder.append(myResDef.getName()); 1055 } 1056 if (theElements == null) { 1057 return true; 1058 } 1059 return theElements.contains(thePathBuilder.toString()); 1060 } else if (myParent != null) { 1061 boolean parentCheck; 1062 if (theCheckingForWhitelist) { 1063 parentCheck = myParent.checkIfParentShouldBeEncodedAndBuildPath(thePathBuilder, theStarPass); 1064 } else { 1065 parentCheck = myParent.checkIfParentShouldNotBeEncodedAndBuildPath(thePathBuilder, theStarPass); 1066 } 1067 if (parentCheck) { 1068 return true; 1069 } 1070 1071 if (myDef != null) { 1072 if (myDef.getMin() > 0) { 1073 if (theElements.contains("*.(mandatory)")) { 1074 return true; 1075 } 1076 } 1077 1078 thePathBuilder.append('.'); 1079 thePathBuilder.append(myDef.getElementName()); 1080 String currentPath = thePathBuilder.toString(); 1081 boolean retVal = theElements.contains(currentPath); 1082 int dotIdx = currentPath.indexOf('.'); 1083 if (!retVal) { 1084 if (dotIdx != -1 && theElements.contains(currentPath.substring(dotIdx + 1))) { 1085 if (!myParent.isSubResource()) { 1086 return true; 1087 } 1088 } 1089 } 1090 return retVal; 1091 } 1092 } 1093 1094 return false; 1095 } 1096 1097 public BaseRuntimeChildDefinition getDef() { 1098 return myDef; 1099 } 1100 1101 public CompositeChildElement getParent() { 1102 return myParent; 1103 } 1104 1105 public RuntimeResourceDefinition getResDef() { 1106 return myResDef; 1107 } 1108 1109 private boolean isSubResource() { 1110 return mySubResource; 1111 } 1112 1113 public boolean shouldBeEncoded() { 1114 boolean retVal = true; 1115 if (myEncodeElements != null) { 1116 retVal = checkIfParentShouldBeEncodedAndBuildPath(new StringBuilder(), false); 1117 if (retVal == false && myEncodeElementsIncludesStars) { 1118 retVal = checkIfParentShouldBeEncodedAndBuildPath(new StringBuilder(), true); 1119 } 1120 } 1121 if (retVal && myDontEncodeElements != null) { 1122 retVal = !checkIfParentShouldNotBeEncodedAndBuildPath(new StringBuilder(), false); 1123 if (retVal && myDontEncodeElementsIncludesStars) { 1124 retVal = !checkIfParentShouldNotBeEncodedAndBuildPath(new StringBuilder(), true); 1125 } 1126 } 1127 // if (retVal == false && myEncodeElements.contains("*.(mandatory)")) { 1128 // if (myDef.getMin() > 0) { 1129 // retVal = true; 1130 // } 1131 // } 1132 1133 return retVal; 1134 } 1135 } 1136 1137 static class ContainedResources { 1138 private long myNextContainedId = 1; 1139 1140 private List<IBaseResource> myResourceList; 1141 private IdentityHashMap<IBaseResource, IIdType> myResourceToIdMap; 1142 private Map<String, IBaseResource> myExistingIdToContainedResourceMap; 1143 1144 public Map<String, IBaseResource> getExistingIdToContainedResource() { 1145 if (myExistingIdToContainedResourceMap == null) { 1146 myExistingIdToContainedResourceMap = new HashMap<>(); 1147 } 1148 return myExistingIdToContainedResourceMap; 1149 } 1150 1151 public void addContained(IBaseResource theResource) { 1152 if (getResourceToIdMap().containsKey(theResource)) { 1153 return; 1154 } 1155 1156 IIdType newId; 1157 if (theResource.getIdElement().isLocal()) { 1158 newId = theResource.getIdElement(); 1159 } else { 1160 newId = null; 1161 } 1162 1163 getResourceToIdMap().put(theResource, newId); 1164 getResourceList().add(theResource); 1165 } 1166 1167 public void addContained(IIdType theId, IBaseResource theResource) { 1168 if (!getResourceToIdMap().containsKey(theResource)) { 1169 getResourceToIdMap().put(theResource, theId); 1170 getResourceList().add(theResource); 1171 } 1172 } 1173 1174 public List<IBaseResource> getContainedResources() { 1175 if (getResourceToIdMap() == null) { 1176 return Collections.emptyList(); 1177 } 1178 return getResourceList(); 1179 } 1180 1181 public IIdType getResourceId(IBaseResource theNext) { 1182 if (getResourceToIdMap() == null) { 1183 return null; 1184 } 1185 return getResourceToIdMap().get(theNext); 1186 } 1187 1188 private List<IBaseResource> getResourceList() { 1189 if (myResourceList == null) { 1190 myResourceList = new ArrayList<>(); 1191 } 1192 return myResourceList; 1193 } 1194 1195 private IdentityHashMap<IBaseResource, IIdType> getResourceToIdMap() { 1196 if (myResourceToIdMap == null) { 1197 myResourceToIdMap = new IdentityHashMap<>(); 1198 } 1199 return myResourceToIdMap; 1200 } 1201 1202 public boolean isEmpty() { 1203 if (myResourceToIdMap == null) { 1204 return true; 1205 } 1206 return myResourceToIdMap.isEmpty(); 1207 } 1208 1209 public boolean hasExistingIdToContainedResource() { 1210 return myExistingIdToContainedResourceMap != null; 1211 } 1212 1213 public void assignIdsToContainedResources() { 1214 1215 if (getResourceList() != null) { 1216 1217 /* 1218 * The idea with the code block below: 1219 * 1220 * We want to preserve any IDs that were user-assigned, so that if it's really 1221 * important to someone that their contained resource have the ID of #FOO 1222 * or #1 we will keep that. 1223 * 1224 * For any contained resources where no ID was assigned by the user, we 1225 * want to manually create an ID but make sure we don't reuse an existing ID. 1226 */ 1227 1228 Set<String> ids = new HashSet<>(); 1229 1230 // Gather any user assigned IDs 1231 for (IBaseResource nextResource : getResourceList()) { 1232 if (getResourceToIdMap().get(nextResource) != null) { 1233 ids.add(getResourceToIdMap().get(nextResource).getValue()); 1234 continue; 1235 } 1236 } 1237 1238 // Automatically assign IDs to the rest 1239 for (IBaseResource nextResource : getResourceList()) { 1240 1241 while (getResourceToIdMap().get(nextResource) == null) { 1242 String nextCandidate = "#" + myNextContainedId; 1243 myNextContainedId++; 1244 if (!ids.add(nextCandidate)) { 1245 continue; 1246 } 1247 1248 getResourceToIdMap().put(nextResource, new IdDt(nextCandidate)); 1249 } 1250 1251 } 1252 1253 } 1254 1255 } 1256 } 1257 1258 protected static <T> List<T> extractMetadataListNotNull(IResource resource, ResourceMetadataKeyEnum<List<T>> key) { 1259 List<? extends T> securityLabels = key.get(resource); 1260 if (securityLabels == null) { 1261 securityLabels = Collections.emptyList(); 1262 } 1263 return new ArrayList<>(securityLabels); 1264 } 1265 1266 static boolean hasExtensions(IBase theElement) { 1267 if (theElement instanceof ISupportsUndeclaredExtensions) { 1268 ISupportsUndeclaredExtensions res = (ISupportsUndeclaredExtensions) theElement; 1269 if (res.getUndeclaredExtensions().size() > 0 || res.getUndeclaredModifierExtensions().size() > 0) { 1270 return true; 1271 } 1272 } 1273 if (theElement instanceof IBaseHasExtensions) { 1274 IBaseHasExtensions res = (IBaseHasExtensions) theElement; 1275 if (res.hasExtension()) { 1276 return true; 1277 } 1278 } 1279 if (theElement instanceof IBaseHasModifierExtensions) { 1280 IBaseHasModifierExtensions res = (IBaseHasModifierExtensions) theElement; 1281 return res.hasModifierExtension(); 1282 } 1283 return false; 1284 } 1285 1286}