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