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