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