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