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