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 if (theResource != null) { 613 Set<String> autoVersionReferencesAtPathExtensions = MetaUtil.getAutoVersionReferencesAtPath( 614 theResource.getMeta(), myContext.getResourceType(theResource)); 615 616 if (!autoVersionReferencesAtPathExtensions.isEmpty() 617 && theCompositeChildElement.anyPathMatches(autoVersionReferencesAtPathExtensions)) { 618 return false; 619 } 620 } 621 622 Boolean stripVersionsFromReferences = myStripVersionsFromReferences; 623 if (stripVersionsFromReferences != null) { 624 return stripVersionsFromReferences; 625 } 626 627 if (!myContext.getParserOptions().isStripVersionsFromReferences()) { 628 return false; 629 } 630 631 Set<String> dontStripVersionsFromReferencesAtPaths = myDontStripVersionsFromReferencesAtPaths; 632 if (dontStripVersionsFromReferencesAtPaths != null 633 && !dontStripVersionsFromReferencesAtPaths.isEmpty() 634 && theCompositeChildElement.anyPathMatches(dontStripVersionsFromReferencesAtPaths)) { 635 return false; 636 } 637 638 dontStripVersionsFromReferencesAtPaths = 639 myContext.getParserOptions().getDontStripVersionsFromReferencesAtPaths(); 640 return dontStripVersionsFromReferencesAtPaths.isEmpty() 641 || !theCompositeChildElement.anyPathMatches(dontStripVersionsFromReferencesAtPaths); 642 } 643 644 @Override 645 public boolean isSummaryMode() { 646 return mySummaryMode; 647 } 648 649 /** 650 * If set to <code>true</code> (default is <code>false</code>), narratives will not be included in the encoded 651 * values. 652 * 653 * @since 1.2 654 */ 655 public boolean isSuppressNarratives() { 656 return mySuppressNarratives; 657 } 658 659 @Override 660 public IBaseResource parseResource(InputStream theInputStream) throws DataFormatException { 661 return parseResource(new InputStreamReader(theInputStream, Charsets.UTF_8)); 662 } 663 664 @Override 665 public <T extends IBaseResource> T parseResource(Class<T> theResourceType, InputStream theInputStream) 666 throws DataFormatException { 667 return parseResource(theResourceType, new InputStreamReader(theInputStream, Constants.CHARSET_UTF8)); 668 } 669 670 @Override 671 public <T extends IBaseResource> T parseResource(Class<T> theResourceType, Reader theReader) 672 throws DataFormatException { 673 674 /* 675 * We do this so that the context can verify that the structure is for 676 * the correct FHIR version 677 */ 678 if (theResourceType != null) { 679 myContext.getResourceDefinition(theResourceType); 680 } 681 682 // Actually do the parse 683 T retVal = doParseResource(theResourceType, theReader); 684 685 RuntimeResourceDefinition def = myContext.getResourceDefinition(retVal); 686 if ("Bundle".equals(def.getName())) { 687 688 if (isOverrideResourceIdWithBundleEntryFullUrl()) { 689 BundleUtil.processEntries(myContext, (IBaseBundle) retVal, t -> { 690 String fullUrl = t.getFullUrl(); 691 if (fullUrl != null) { 692 IBaseResource resource = t.getResource(); 693 if (resource != null) { 694 IIdType resourceId = resource.getIdElement(); 695 if (isBlank(resourceId.getValue())) { 696 resourceId.setValue(fullUrl); 697 } else { 698 if (fullUrl.startsWith("urn:") 699 && fullUrl.length() 700 > resourceId.getIdPart().length() 701 && fullUrl.charAt(fullUrl.length() 702 - resourceId.getIdPart().length() 703 - 1) 704 == ':' 705 && fullUrl.endsWith(resourceId.getIdPart())) { 706 resourceId.setValue(fullUrl); 707 } else { 708 IIdType fullUrlId = myContext.getVersion().newIdType(); 709 fullUrlId.setValue(fullUrl); 710 if (myContext.getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3)) { 711 IIdType newId = fullUrlId; 712 if (!newId.hasVersionIdPart() && resourceId.hasVersionIdPart()) { 713 newId = newId.withVersion(resourceId.getVersionIdPart()); 714 } 715 resourceId.setValue(newId.getValue()); 716 } else if (StringUtils.equals(fullUrlId.getIdPart(), resourceId.getIdPart())) { 717 if (fullUrlId.hasBaseUrl()) { 718 IIdType newResourceId = resourceId.withServerBase( 719 fullUrlId.getBaseUrl(), resourceId.getResourceType()); 720 resourceId.setValue(newResourceId.getValue()); 721 } 722 } 723 } 724 } 725 } 726 } 727 }); 728 } 729 } 730 731 return retVal; 732 } 733 734 @SuppressWarnings("cast") 735 @Override 736 public <T extends IBaseResource> T parseResource(Class<T> theResourceType, String theMessageString) { 737 StringReader reader = new StringReader(theMessageString); 738 return parseResource(theResourceType, reader); 739 } 740 741 @Override 742 public IBaseResource parseResource(Reader theReader) throws ConfigurationException, DataFormatException { 743 return parseResource(null, theReader); 744 } 745 746 @Override 747 public IBaseResource parseResource(String theMessageString) throws ConfigurationException, DataFormatException { 748 return parseResource(null, theMessageString); 749 } 750 751 protected List<? extends IBase> preProcessValues( 752 BaseRuntimeChildDefinition theMetaChildUncast, 753 IBaseResource theResource, 754 List<? extends IBase> theValues, 755 CompositeChildElement theCompositeChildElement, 756 EncodeContext theEncodeContext) { 757 if (myContext.getVersion().getVersion().isRi()) { 758 759 /* 760 * If we're encoding the meta tag, we do some massaging of the meta values before 761 * encoding. But if there is no meta element at all, we create one since we're possibly going to be 762 * adding things to it 763 */ 764 if (theValues.isEmpty() && theMetaChildUncast.getElementName().equals("meta")) { 765 BaseRuntimeElementDefinition<?> metaChild = theMetaChildUncast.getChildByName("meta"); 766 if (IBaseMetaType.class.isAssignableFrom(metaChild.getImplementingClass())) { 767 IBaseMetaType newType = (IBaseMetaType) metaChild.newInstance(); 768 theValues = Collections.singletonList(newType); 769 } 770 } 771 772 if (theValues.size() == 1 && theValues.get(0) instanceof IBaseMetaType) { 773 774 IBaseMetaType metaValue = (IBaseMetaType) theValues.get(0); 775 try { 776 metaValue = (IBaseMetaType) 777 metaValue.getClass().getMethod("copy").invoke(metaValue); 778 } catch (Exception e) { 779 throw new InternalErrorException(Msg.code(1830) + "Failed to duplicate meta", e); 780 } 781 782 if (isBlank(metaValue.getVersionId())) { 783 if (theResource.getIdElement().hasVersionIdPart()) { 784 metaValue.setVersionId(theResource.getIdElement().getVersionIdPart()); 785 } 786 } 787 788 filterCodingsWithNoCodeOrSystem(metaValue.getTag()); 789 filterCodingsWithNoCodeOrSystem(metaValue.getSecurity()); 790 791 List<? extends IPrimitiveType<String>> newProfileList = 792 getProfileTagsForEncoding(theResource, metaValue.getProfile()); 793 List<? extends IPrimitiveType<String>> oldProfileList = metaValue.getProfile(); 794 if (oldProfileList != newProfileList) { 795 oldProfileList.clear(); 796 for (IPrimitiveType<String> next : newProfileList) { 797 if (isNotBlank(next.getValue())) { 798 metaValue.addProfile(next.getValue()); 799 } 800 } 801 } 802 803 if (shouldAddSubsettedTag(theEncodeContext)) { 804 IBaseCoding coding = metaValue.addTag(); 805 coding.setCode(Constants.TAG_SUBSETTED_CODE); 806 coding.setSystem(getSubsettedCodeSystem()); 807 coding.setDisplay(subsetDescription()); 808 } 809 810 return Collections.singletonList(metaValue); 811 } 812 } 813 814 @SuppressWarnings("unchecked") 815 List<IBase> retVal = (List<IBase>) theValues; 816 817 for (int i = 0; i < retVal.size(); i++) { 818 IBase next = retVal.get(i); 819 820 /* 821 * If we have automatically contained any resources via 822 * their references, this ensures that we output the new 823 * local reference 824 */ 825 if (next instanceof IBaseReference) { 826 IBaseReference nextRef = (IBaseReference) next; 827 String refText = determineReferenceText(nextRef, theCompositeChildElement, theResource); 828 if (!StringUtils.equals(refText, nextRef.getReferenceElement().getValue())) { 829 830 if (retVal == theValues) { 831 retVal = new ArrayList<>(theValues); 832 } 833 IBaseReference newRef = (IBaseReference) 834 myContext.getElementDefinition(nextRef.getClass()).newInstance(); 835 myContext.newTerser().cloneInto(nextRef, newRef, true); 836 newRef.setReference(refText); 837 retVal.set(i, newRef); 838 } 839 } 840 } 841 842 return retVal; 843 } 844 845 private String getSubsettedCodeSystem() { 846 if (myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) { 847 return Constants.TAG_SUBSETTED_SYSTEM_R4; 848 } else { 849 return Constants.TAG_SUBSETTED_SYSTEM_DSTU3; 850 } 851 } 852 853 @Override 854 public IParser setDontStripVersionsFromReferencesAtPaths(String... thePaths) { 855 if (thePaths == null) { 856 setDontStripVersionsFromReferencesAtPaths((List<String>) null); 857 } else { 858 setDontStripVersionsFromReferencesAtPaths(Arrays.asList(thePaths)); 859 } 860 return this; 861 } 862 863 @SuppressWarnings("unchecked") 864 @Override 865 public IParser setDontStripVersionsFromReferencesAtPaths(Collection<String> thePaths) { 866 if (thePaths == null) { 867 myDontStripVersionsFromReferencesAtPaths = Collections.emptySet(); 868 } else if (thePaths instanceof HashSet) { 869 myDontStripVersionsFromReferencesAtPaths = (Set<String>) ((HashSet<String>) thePaths).clone(); 870 } else { 871 myDontStripVersionsFromReferencesAtPaths = new HashSet<>(thePaths); 872 } 873 return this; 874 } 875 876 @Override 877 public IParser setOmitResourceId(boolean theOmitResourceId) { 878 myOmitResourceId = theOmitResourceId; 879 return this; 880 } 881 882 @Override 883 public IParser setOverrideResourceIdWithBundleEntryFullUrl(Boolean theOverrideResourceIdWithBundleEntryFullUrl) { 884 myOverrideResourceIdWithBundleEntryFullUrl = theOverrideResourceIdWithBundleEntryFullUrl; 885 return this; 886 } 887 888 @Override 889 public IParser setParserErrorHandler(IParserErrorHandler theErrorHandler) { 890 Validate.notNull(theErrorHandler, "theErrorHandler must not be null"); 891 myErrorHandler = theErrorHandler; 892 return this; 893 } 894 895 @Override 896 public IParser setServerBaseUrl(String theUrl) { 897 myServerBaseUrl = isNotBlank(theUrl) ? theUrl : null; 898 return this; 899 } 900 901 @Override 902 public IParser setStripVersionsFromReferences(Boolean theStripVersionsFromReferences) { 903 myStripVersionsFromReferences = theStripVersionsFromReferences; 904 return this; 905 } 906 907 @Override 908 public IParser setSummaryMode(boolean theSummaryMode) { 909 mySummaryMode = theSummaryMode; 910 return this; 911 } 912 913 @Override 914 public IParser setSuppressNarratives(boolean theSuppressNarratives) { 915 mySuppressNarratives = theSuppressNarratives; 916 return this; 917 } 918 919 protected boolean shouldAddSubsettedTag(EncodeContext theEncodeContext) { 920 if (isSummaryMode()) { 921 return true; 922 } 923 if (isSuppressNarratives()) { 924 return true; 925 } 926 if (myEncodeElements != null) { 927 if (isEncodeElementsAppliesToChildResourcesOnly() 928 && theEncodeContext.getResourcePath().size() < 2) { 929 return false; 930 } 931 932 String currentResourceName = theEncodeContext 933 .getResourcePath() 934 .get(theEncodeContext.getResourcePath().size() - 1) 935 .getName(); 936 return myEncodeElementsAppliesToResourceTypes == null 937 || myEncodeElementsAppliesToResourceTypes.contains(currentResourceName); 938 } 939 940 return false; 941 } 942 943 protected boolean shouldEncodeResourceId(IBaseResource theResource, EncodeContext theEncodeContext) { 944 boolean retVal = true; 945 if (isOmitResourceId() && theEncodeContext.getPath().size() == 1) { 946 retVal = false; 947 } else { 948 if (myDontEncodeElements != null) { 949 String resourceName = myContext.getResourceType(theResource); 950 if (myDontEncodeElements.stream().anyMatch(t -> t.equalsPath(resourceName + ".id"))) { 951 retVal = false; 952 } else if (myDontEncodeElements.stream().anyMatch(t -> t.equalsPath("*.id"))) { 953 retVal = false; 954 } else if (theEncodeContext.getResourcePath().size() == 1 955 && myDontEncodeElements.stream().anyMatch(t -> t.equalsPath("id"))) { 956 retVal = false; 957 } 958 } 959 } 960 return retVal; 961 } 962 963 /** 964 * Used for DSTU2 only 965 */ 966 protected boolean shouldEncodeResourceMeta(IResource theResource) { 967 return shouldEncodePath(theResource, "meta"); 968 } 969 970 /** 971 * Used for DSTU2 only 972 */ 973 protected boolean shouldEncodePath(IResource theResource, String thePath) { 974 if (myDontEncodeElements != null) { 975 String resourceName = myContext.getResourceType(theResource); 976 if (myDontEncodeElements.stream().anyMatch(t -> t.equalsPath(resourceName + "." + thePath))) { 977 return false; 978 } else return myDontEncodeElements.stream().noneMatch(t -> t.equalsPath("*." + thePath)); 979 } 980 return true; 981 } 982 983 private String subsetDescription() { 984 return "Resource encoded in summary mode"; 985 } 986 987 protected void throwExceptionForUnknownChildType( 988 BaseRuntimeChildDefinition nextChild, Class<? extends IBase> theType) { 989 if (nextChild instanceof BaseRuntimeDeclaredChildDefinition) { 990 StringBuilder b = new StringBuilder(); 991 b.append(nextChild.getElementName()); 992 b.append(" has type "); 993 b.append(theType.getName()); 994 b.append(" but this is not a valid type for this element"); 995 if (nextChild instanceof RuntimeChildChoiceDefinition) { 996 RuntimeChildChoiceDefinition choice = (RuntimeChildChoiceDefinition) nextChild; 997 b.append(" - Expected one of: " + choice.getValidChildTypes()); 998 } 999 throw new DataFormatException(Msg.code(1831) + b); 1000 } 1001 throw new DataFormatException(Msg.code(1832) + nextChild + " has no child of type " + theType); 1002 } 1003 1004 protected boolean shouldEncodeResource(String theName) { 1005 if (myDontEncodeElements != null) { 1006 for (EncodeContextPath next : myDontEncodeElements) { 1007 if (next.equalsPath(theName)) { 1008 return false; 1009 } 1010 } 1011 } 1012 return true; 1013 } 1014 1015 protected boolean isFhirVersionLessThanOrEqualTo(FhirVersionEnum theFhirVersionEnum) { 1016 final FhirVersionEnum apiFhirVersion = myContext.getVersion().getVersion(); 1017 return theFhirVersionEnum == apiFhirVersion || apiFhirVersion.isOlderThan(theFhirVersionEnum); 1018 } 1019 1020 protected void containResourcesInReferences(IBaseResource theResource) { 1021 1022 /* 1023 * If a UUID is present in Bundle.entry.fullUrl but no value is present 1024 * in Bundle.entry.resource.id, the resource has a discrete identity which 1025 * should be copied into the resource ID. It will not be serialized in 1026 * Resource.id because it's a placeholder/UUID value, but its presence there 1027 * informs the serializer that we don't need to contain this resource. 1028 */ 1029 if (theResource instanceof IBaseBundle) { 1030 List<Pair<String, IBaseResource>> entries = 1031 BundleUtil.getBundleEntryFullUrlsAndResources(getContext(), (IBaseBundle) theResource); 1032 for (Pair<String, IBaseResource> nextEntry : entries) { 1033 String fullUrl = nextEntry.getKey(); 1034 IBaseResource resource = nextEntry.getValue(); 1035 if (startsWith(fullUrl, "urn:")) { 1036 if (resource != null && resource.getIdElement().getValue() == null) { 1037 resource.getIdElement().setValue(fullUrl); 1038 } 1039 } 1040 } 1041 } 1042 1043 myContainedResources = getContext().newTerser().containResources(theResource); 1044 } 1045 1046 class ChildNameAndDef { 1047 1048 private final BaseRuntimeElementDefinition<?> myChildDef; 1049 private final String myChildName; 1050 1051 public ChildNameAndDef(String theChildName, BaseRuntimeElementDefinition<?> theChildDef) { 1052 myChildName = theChildName; 1053 myChildDef = theChildDef; 1054 } 1055 1056 public BaseRuntimeElementDefinition<?> getChildDef() { 1057 return myChildDef; 1058 } 1059 1060 public String getChildName() { 1061 return myChildName; 1062 } 1063 } 1064 1065 /** 1066 * EncodeContext is a shared state object that is passed around the 1067 * encode process 1068 */ 1069 public class EncodeContext extends EncodeContextPath { 1070 private final Map<Key, List<BaseParser.CompositeChildElement>> myCompositeChildrenCache = new HashMap<>(); 1071 1072 public Map<Key, List<BaseParser.CompositeChildElement>> getCompositeChildrenCache() { 1073 return myCompositeChildrenCache; 1074 } 1075 } 1076 1077 protected class CompositeChildElement { 1078 private final BaseRuntimeChildDefinition myDef; 1079 private final CompositeChildElement myParent; 1080 private final RuntimeResourceDefinition myResDef; 1081 private final EncodeContext myEncodeContext; 1082 1083 public CompositeChildElement( 1084 CompositeChildElement theParent, 1085 @Nullable BaseRuntimeChildDefinition theDef, 1086 EncodeContext theEncodeContext) { 1087 myDef = theDef; 1088 myParent = theParent; 1089 myResDef = null; 1090 myEncodeContext = theEncodeContext; 1091 1092 if (ourLog.isTraceEnabled()) { 1093 if (theParent != null) { 1094 StringBuilder path = theParent.buildPath(); 1095 if (path != null) { 1096 path.append('.'); 1097 if (myDef != null) { 1098 path.append(myDef.getElementName()); 1099 } 1100 ourLog.trace(" * Next path: {}", path); 1101 } 1102 } 1103 } 1104 } 1105 1106 public CompositeChildElement(RuntimeResourceDefinition theResDef, EncodeContext theEncodeContext) { 1107 myResDef = theResDef; 1108 myDef = null; 1109 myParent = null; 1110 myEncodeContext = theEncodeContext; 1111 } 1112 1113 @Override 1114 public String toString() { 1115 return myDef.getElementName(); 1116 } 1117 1118 private void addParent(CompositeChildElement theParent, StringBuilder theB) { 1119 if (theParent != null) { 1120 if (theParent.myResDef != null) { 1121 theB.append(theParent.myResDef.getName()); 1122 return; 1123 } 1124 1125 if (theParent.myParent != null) { 1126 addParent(theParent.myParent, theB); 1127 } 1128 1129 if (theParent.myDef != null) { 1130 if (theB.length() > 0) { 1131 theB.append('.'); 1132 } 1133 theB.append(theParent.myDef.getElementName()); 1134 } 1135 } 1136 } 1137 1138 public boolean anyPathMatches(Set<String> thePaths) { 1139 StringBuilder b = new StringBuilder(); 1140 addParent(this, b); 1141 1142 String path = b.toString(); 1143 return thePaths.contains(path); 1144 } 1145 1146 private StringBuilder buildPath() { 1147 if (myResDef != null) { 1148 StringBuilder b = new StringBuilder(); 1149 b.append(myResDef.getName()); 1150 return b; 1151 } else if (myParent != null) { 1152 StringBuilder b = myParent.buildPath(); 1153 if (b != null && myDef != null) { 1154 b.append('.'); 1155 b.append(myDef.getElementName()); 1156 } 1157 return b; 1158 } else { 1159 return null; 1160 } 1161 } 1162 1163 private boolean checkIfParentShouldBeEncodedAndBuildPath() { 1164 List<EncodeContextPath> encodeElements = myEncodeElements; 1165 1166 String currentResourceName = myEncodeContext 1167 .getResourcePath() 1168 .get(myEncodeContext.getResourcePath().size() - 1) 1169 .getName(); 1170 if (myEncodeElementsAppliesToResourceTypes != null 1171 && !myEncodeElementsAppliesToResourceTypes.contains(currentResourceName)) { 1172 encodeElements = null; 1173 } 1174 1175 boolean retVal = checkIfPathMatchesForEncoding(encodeElements, true); 1176 1177 /* 1178 * We force the meta tag to be encoded even if it's not specified as an element in the 1179 * elements filter, specifically because we'll need it in order to automatically add 1180 * the SUBSETTED tag 1181 */ 1182 if (!retVal) { 1183 if ("meta".equals(myEncodeContext.getLeafResourcePathFirstField()) 1184 && shouldAddSubsettedTag(myEncodeContext)) { 1185 // The next element is a child of the <meta> element 1186 retVal = true; 1187 } else if ("meta".equals(myDef.getElementName()) && shouldAddSubsettedTag(myEncodeContext)) { 1188 // The next element is the <meta> element 1189 retVal = true; 1190 } 1191 } 1192 1193 return retVal; 1194 } 1195 1196 private boolean checkIfParentShouldNotBeEncodedAndBuildPath() { 1197 return checkIfPathMatchesForEncoding(myDontEncodeElements, false); 1198 } 1199 1200 private boolean checkIfPathMatchesForEncoding( 1201 List<EncodeContextPath> theElements, boolean theCheckingForEncodeElements) { 1202 1203 boolean retVal = false; 1204 if (myDef != null) { 1205 myEncodeContext.pushPath(myDef.getElementName(), false); 1206 } 1207 1208 if (theCheckingForEncodeElements 1209 && isEncodeElementsAppliesToChildResourcesOnly() 1210 && myEncodeContext.getResourcePath().size() < 2) { 1211 retVal = true; 1212 } else if (theElements == null) { 1213 retVal = true; 1214 } else { 1215 EncodeContextPath currentResourcePath = myEncodeContext.getCurrentResourcePath(); 1216 ourLog.trace("Current resource path: {}", currentResourcePath); 1217 for (EncodeContextPath next : theElements) { 1218 1219 if (next.startsWith(currentResourcePath, true)) { 1220 if (theCheckingForEncodeElements 1221 || next.getPath().size() 1222 == currentResourcePath.getPath().size()) { 1223 retVal = true; 1224 break; 1225 } 1226 } 1227 1228 if (next.getPath().get(next.getPath().size() - 1).getName().equals("(mandatory)")) { 1229 if (myDef.getMin() > 0) { 1230 retVal = true; 1231 break; 1232 } 1233 if (currentResourcePath.getPath().size() 1234 > next.getPath().size()) { 1235 retVal = true; 1236 break; 1237 } 1238 } 1239 } 1240 } 1241 1242 if (myDef != null) { 1243 myEncodeContext.popPath(); 1244 } 1245 1246 return retVal; 1247 } 1248 1249 public BaseRuntimeChildDefinition getDef() { 1250 return myDef; 1251 } 1252 1253 public CompositeChildElement getParent() { 1254 return myParent; 1255 } 1256 1257 public boolean shouldBeEncoded(boolean theContainedResource) { 1258 boolean retVal = true; 1259 if (myEncodeElements != null) { 1260 retVal = checkIfParentShouldBeEncodedAndBuildPath(); 1261 } 1262 if (retVal && myDontEncodeElements != null) { 1263 retVal = !checkIfParentShouldNotBeEncodedAndBuildPath(); 1264 } 1265 if (theContainedResource) { 1266 retVal = !notEncodeForContainedResource.contains(myDef.getElementName()); 1267 } 1268 if (retVal && isSummaryMode() && (getDef() == null || !getDef().isSummary())) { 1269 String resourceName = myEncodeContext.getLeafResourceName(); 1270 // Technically the spec says we shouldn't include extensions in CapabilityStatement 1271 // but we will do so because there are people who depend on this behaviour, at least 1272 // as of 2019-07. See 1273 // https://github.com/smart-on-fhir/Swift-FHIR/issues/26 1274 // for example. 1275 if (("Conformance".equals(resourceName) || "CapabilityStatement".equals(resourceName)) 1276 && ("extension".equals(myDef.getElementName()) 1277 || "extension".equals(myEncodeContext.getLeafElementName()))) { 1278 // skip 1279 } else { 1280 retVal = false; 1281 } 1282 } 1283 1284 return retVal; 1285 } 1286 1287 @Override 1288 public int hashCode() { 1289 final int prime = 31; 1290 int result = 1; 1291 result = prime * result + ((myDef == null) ? 0 : myDef.hashCode()); 1292 result = prime * result + ((myParent == null) ? 0 : myParent.hashCode()); 1293 result = prime * result + ((myResDef == null) ? 0 : myResDef.hashCode()); 1294 result = prime * result + ((myEncodeContext == null) ? 0 : myEncodeContext.hashCode()); 1295 return result; 1296 } 1297 1298 @Override 1299 public boolean equals(Object obj) { 1300 if (this == obj) return true; 1301 1302 if (obj instanceof CompositeChildElement) { 1303 final CompositeChildElement that = (CompositeChildElement) obj; 1304 return Objects.equals(this.getEnclosingInstance(), that.getEnclosingInstance()) 1305 && Objects.equals(this.myDef, that.myDef) 1306 && Objects.equals(this.myParent, that.myParent) 1307 && Objects.equals(this.myResDef, that.myResDef) 1308 && Objects.equals(this.myEncodeContext, that.myEncodeContext); 1309 } 1310 return false; 1311 } 1312 1313 private BaseParser getEnclosingInstance() { 1314 return BaseParser.this; 1315 } 1316 } 1317 1318 private static class Key { 1319 private final BaseRuntimeElementCompositeDefinition<?> resDef; 1320 private final boolean theContainedResource; 1321 private final BaseParser.CompositeChildElement theParent; 1322 private final BaseParser.EncodeContext theEncodeContext; 1323 1324 public Key( 1325 BaseRuntimeElementCompositeDefinition<?> resDef, 1326 final boolean theContainedResource, 1327 final BaseParser.CompositeChildElement theParent, 1328 BaseParser.EncodeContext theEncodeContext) { 1329 this.resDef = resDef; 1330 this.theContainedResource = theContainedResource; 1331 this.theParent = theParent; 1332 this.theEncodeContext = theEncodeContext; 1333 } 1334 1335 @Override 1336 public int hashCode() { 1337 final int prime = 31; 1338 int result = 1; 1339 result = prime * result + ((resDef == null) ? 0 : resDef.hashCode()); 1340 result = prime * result + (theContainedResource ? 1231 : 1237); 1341 result = prime * result + ((theParent == null) ? 0 : theParent.hashCode()); 1342 result = prime * result + ((theEncodeContext == null) ? 0 : theEncodeContext.hashCode()); 1343 return result; 1344 } 1345 1346 @Override 1347 public boolean equals(final Object obj) { 1348 if (this == obj) { 1349 return true; 1350 } 1351 if (obj instanceof Key) { 1352 final Key that = (Key) obj; 1353 return Objects.equals(this.resDef, that.resDef) 1354 && this.theContainedResource == that.theContainedResource 1355 && Objects.equals(this.theParent, that.theParent) 1356 && Objects.equals(this.theEncodeContext, that.theEncodeContext); 1357 } 1358 return false; 1359 } 1360 } 1361 1362 protected static <T> List<T> extractMetadataListNotNull(IResource resource, ResourceMetadataKeyEnum<List<T>> key) { 1363 List<? extends T> securityLabels = key.get(resource); 1364 if (securityLabels == null) { 1365 securityLabels = Collections.emptyList(); 1366 } 1367 return new ArrayList<>(securityLabels); 1368 } 1369 1370 static boolean hasNoExtensions(IBase theElement) { 1371 if (theElement instanceof ISupportsUndeclaredExtensions) { 1372 ISupportsUndeclaredExtensions res = (ISupportsUndeclaredExtensions) theElement; 1373 if (res.getUndeclaredExtensions().size() > 0 1374 || res.getUndeclaredModifierExtensions().size() > 0) { 1375 return false; 1376 } 1377 } 1378 if (theElement instanceof IBaseHasExtensions) { 1379 IBaseHasExtensions res = (IBaseHasExtensions) theElement; 1380 if (res.hasExtension()) { 1381 return false; 1382 } 1383 } 1384 if (theElement instanceof IBaseHasModifierExtensions) { 1385 IBaseHasModifierExtensions res = (IBaseHasModifierExtensions) theElement; 1386 return !res.hasModifierExtension(); 1387 } 1388 return true; 1389 } 1390}