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