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