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