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