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