001package ca.uhn.fhir.parser; 002 003/* 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2016 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.util.ArrayList; 031import java.util.Collections; 032import java.util.HashMap; 033import java.util.HashSet; 034import java.util.IdentityHashMap; 035import java.util.Iterator; 036import java.util.List; 037import java.util.Map; 038import java.util.Set; 039 040import org.apache.commons.lang3.StringUtils; 041import org.apache.commons.lang3.Validate; 042import org.hl7.fhir.instance.model.api.IAnyResource; 043import org.hl7.fhir.instance.model.api.IBase; 044import org.hl7.fhir.instance.model.api.IBaseCoding; 045import org.hl7.fhir.instance.model.api.IBaseMetaType; 046import org.hl7.fhir.instance.model.api.IBaseReference; 047import org.hl7.fhir.instance.model.api.IBaseResource; 048import org.hl7.fhir.instance.model.api.IDomainResource; 049import org.hl7.fhir.instance.model.api.IIdType; 050import org.hl7.fhir.instance.model.api.IPrimitiveType; 051 052import ca.uhn.fhir.context.BaseRuntimeChildDefinition; 053import ca.uhn.fhir.context.BaseRuntimeDeclaredChildDefinition; 054import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; 055import ca.uhn.fhir.context.BaseRuntimeElementDefinition; 056import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum; 057import ca.uhn.fhir.context.ConfigurationException; 058import ca.uhn.fhir.context.FhirContext; 059import ca.uhn.fhir.context.FhirVersionEnum; 060import ca.uhn.fhir.context.RuntimeChildChoiceDefinition; 061import ca.uhn.fhir.context.RuntimeChildContainedResources; 062import ca.uhn.fhir.context.RuntimeChildNarrativeDefinition; 063import ca.uhn.fhir.context.RuntimeResourceDefinition; 064import ca.uhn.fhir.model.api.Bundle; 065import ca.uhn.fhir.model.api.BundleEntry; 066import ca.uhn.fhir.model.api.IResource; 067import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; 068import ca.uhn.fhir.model.api.Tag; 069import ca.uhn.fhir.model.api.TagList; 070import ca.uhn.fhir.model.primitive.IdDt; 071import ca.uhn.fhir.rest.server.Constants; 072import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 073 074public abstract class BaseParser implements IParser { 075 076 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseParser.class); 077 private ContainedResources myContainedResources; 078 private FhirContext myContext; 079 private Set<String> myEncodeElements; 080 private Set<String> myEncodeElementsAppliesToResourceTypes; 081 private boolean myEncodeElementsIncludesStars; 082 private IParserErrorHandler myErrorHandler; 083 private boolean myOmitResourceId; 084 private String myServerBaseUrl; 085 private boolean myStripVersionsFromReferences = true; 086 private boolean mySummaryMode; 087 private boolean mySuppressNarratives; 088 089 /** 090 * Constructor 091 * 092 * @param theParserErrorHandler 093 */ 094 public BaseParser(FhirContext theContext, IParserErrorHandler theParserErrorHandler) { 095 myContext = theContext; 096 myErrorHandler = theParserErrorHandler; 097 } 098 099 protected Iterable<CompositeChildElement> compositeChildIterator(final List<? extends BaseRuntimeChildDefinition> theChildren, final boolean theContainedResource, final CompositeChildElement theParent) { 100 return new Iterable<BaseParser.CompositeChildElement>() { 101 @Override 102 public Iterator<CompositeChildElement> iterator() { 103 104 return new Iterator<CompositeChildElement>() { 105 private Iterator<? extends BaseRuntimeChildDefinition> myChildrenIter; 106 private Boolean myHasNext = null; 107 private CompositeChildElement myNext; 108 109 /** 110 * Constructor 111 */ 112 { 113 myChildrenIter = theChildren.iterator(); 114 } 115 116 @Override 117 public boolean hasNext() { 118 if (myHasNext != null) { 119 return myHasNext; 120 } 121 122 myNext = null; 123 do { 124 if (myChildrenIter.hasNext() == false) { 125 myHasNext = Boolean.FALSE; 126 return false; 127 } 128 129 myNext = new CompositeChildElement(theParent, myChildrenIter.next()); 130 131 /* 132 * There are lots of reasons we might skip encoding a particular child 133 */ 134 if (myNext.getDef().getElementName().equals("extension") || myNext.getDef().getElementName().equals("modifierExtension")) { 135 myNext = null; 136 } else if (myNext.getDef().getElementName().equals("id")) { 137 myNext = null; 138 } else if (!myNext.shouldBeEncoded()) { 139 myNext = null; 140 } else if (isSummaryMode() && !myNext.getDef().isSummary()) { 141 myNext = null; 142 } else if (myNext.getDef() instanceof RuntimeChildNarrativeDefinition) { 143 if (isSuppressNarratives() || isSummaryMode()) { 144 myNext = null; 145 } else if (theContainedResource) { 146 myNext = null; 147 } 148 } else if (myNext.getDef() instanceof RuntimeChildContainedResources) { 149 if (theContainedResource) { 150 myNext = null; 151 } 152 } 153 154 } while (myNext == null); 155 156 myHasNext = true; 157 return true; 158 } 159 160 @Override 161 public CompositeChildElement next() { 162 if (myHasNext == null) { 163 if (!hasNext()) { 164 throw new IllegalStateException(); 165 } 166 } 167 CompositeChildElement retVal = myNext; 168 myNext = null; 169 myHasNext = null; 170 return retVal; 171 } 172 173 @Override 174 public void remove() { 175 throw new UnsupportedOperationException(); 176 } 177 }; 178 } 179 }; 180 } 181 182 private void containResourcesForEncoding(ContainedResources theContained, IBaseResource theResource, IBaseResource theTarget) { 183 Set<String> allIds = new HashSet<String>(); 184 Map<String, IBaseResource> existingIdToContainedResource = null; 185 186 if (theTarget instanceof IResource) { 187 List<? extends IResource> containedResources = ((IResource) theTarget).getContained().getContainedResources(); 188 for (IResource next : containedResources) { 189 String nextId = next.getId().getValue(); 190 if (StringUtils.isNotBlank(nextId)) { 191 if (!nextId.startsWith("#")) { 192 nextId = '#' + nextId; 193 } 194 allIds.add(nextId); 195 if (existingIdToContainedResource == null) { 196 existingIdToContainedResource = new HashMap<String, IBaseResource>(); 197 } 198 existingIdToContainedResource.put(nextId, next); 199 } 200 } 201 } else if (theTarget instanceof IDomainResource) { 202 List<? extends IAnyResource> containedResources = ((IDomainResource) theTarget).getContained(); 203 for (IAnyResource next : containedResources) { 204 String nextId = next.getIdElement().getValue(); 205 if (StringUtils.isNotBlank(nextId)) { 206 if (!nextId.startsWith("#")) { 207 nextId = '#' + nextId; 208 } 209 allIds.add(nextId); 210 if (existingIdToContainedResource == null) { 211 existingIdToContainedResource = new HashMap<String, IBaseResource>(); 212 } 213 existingIdToContainedResource.put(nextId, next); 214 } 215 } 216 } else { 217 // no resources to contain 218 } 219 220 { 221 List<IBaseReference> allElements = myContext.newTerser().getAllPopulatedChildElementsOfType(theResource, IBaseReference.class); 222 for (IBaseReference next : allElements) { 223 IBaseResource resource = next.getResource(); 224 if (resource != null) { 225 if (resource.getIdElement().isEmpty() || resource.getIdElement().isLocal()) { 226 if (theContained.getResourceId(resource) != null) { 227 // Prevent infinite recursion if there are circular loops in the contained resources 228 continue; 229 } 230 theContained.addContained(resource); 231 } else { 232 continue; 233 } 234 235 containResourcesForEncoding(theContained, resource, theTarget); 236 } else if (next.getReferenceElement().isLocal()) { 237 if (existingIdToContainedResource != null) { 238 IBaseResource potentialTarget = existingIdToContainedResource.remove(next.getReferenceElement().getValue()); 239 if (potentialTarget != null) { 240 theContained.addContained(next.getReferenceElement(), potentialTarget); 241 containResourcesForEncoding(theContained, potentialTarget, theTarget); 242 } 243 } 244 } 245 } 246 } 247 248 } 249 250 protected void containResourcesForEncoding(IBaseResource theResource) { 251 ContainedResources contained = new ContainedResources(); 252 containResourcesForEncoding(contained, theResource, theResource); 253 myContainedResources = contained; 254 } 255 256 protected String determineReferenceText(IBaseReference theRef) { 257 IIdType ref = theRef.getReferenceElement(); 258 if (isBlank(ref.getIdPart())) { 259 String reference = ref.getValue(); 260 if (theRef.getResource() != null) { 261 IIdType containedId = getContainedResources().getResourceId(theRef.getResource()); 262 if (containedId != null && !containedId.isEmpty()) { 263 if (containedId.isLocal()) { 264 reference = containedId.getValue(); 265 } else { 266 reference = "#" + containedId.getValue(); 267 } 268 } else { 269 IIdType refId = theRef.getResource().getIdElement(); 270 if (refId != null) { 271 if (refId.hasIdPart()) { 272 if (!refId.hasResourceType()) { 273 refId = refId.withResourceType(myContext.getResourceDefinition(theRef.getResource()).getName()); 274 } 275 if (isStripVersionsFromReferences()) { 276 reference = refId.toVersionless().getValue(); 277 } else { 278 reference = refId.getValue(); 279 } 280 } 281 } 282 } 283 } 284 return reference; 285 } else { 286 if (!ref.hasResourceType() && !ref.isLocal() && theRef.getResource() != null) { 287 ref = ref.withResourceType(myContext.getResourceDefinition(theRef.getResource()).getName()); 288 } 289 if (isNotBlank(myServerBaseUrl) && StringUtils.equals(myServerBaseUrl, ref.getBaseUrl())) { 290 if (isStripVersionsFromReferences()) { 291 return ref.toUnqualifiedVersionless().getValue(); 292 } else { 293 return ref.toUnqualified().getValue(); 294 } 295 } else { 296 if (isStripVersionsFromReferences()) { 297 return ref.toVersionless().getValue(); 298 } else { 299 return ref.getValue(); 300 } 301 } 302 } 303 } 304 305 protected String determineResourceBaseUrl(String bundleBaseUrl, BundleEntry theEntry) { 306 IResource resource = theEntry.getResource(); 307 if (resource == null) { 308 return null; 309 } 310 311 String resourceBaseUrl = null; 312 if (resource.getId() != null && resource.getId().hasBaseUrl()) { 313 if (!resource.getId().getBaseUrl().equals(bundleBaseUrl)) { 314 resourceBaseUrl = resource.getId().getBaseUrl(); 315 } 316 } 317 return resourceBaseUrl; 318 } 319 320 protected abstract void doEncodeBundleToWriter(Bundle theBundle, Writer theWriter) throws IOException, DataFormatException; 321 322 protected abstract void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws IOException, DataFormatException; 323 324 protected abstract <T extends IBaseResource> T doParseResource(Class<T> theResourceType, Reader theReader) throws DataFormatException; 325 326 @Override 327 public String encodeBundleToString(Bundle theBundle) throws DataFormatException { 328 if (theBundle == null) { 329 throw new NullPointerException("Bundle can not be null"); 330 } 331 StringWriter stringWriter = new StringWriter(); 332 try { 333 encodeBundleToWriter(theBundle, stringWriter); 334 } catch (IOException e) { 335 throw new Error("Encountered IOException during write to string - This should not happen!"); 336 } 337 338 return stringWriter.toString(); 339 } 340 341 @Override 342 public final void encodeBundleToWriter(Bundle theBundle, Writer theWriter) throws IOException, DataFormatException { 343 Validate.notNull(theBundle, "theBundle must not be null"); 344 Validate.notNull(theWriter, "theWriter must not be null"); 345 doEncodeBundleToWriter(theBundle, theWriter); 346 } 347 348 @Override 349 public String encodeResourceToString(IBaseResource theResource) throws DataFormatException { 350 Writer stringWriter = new StringWriter(); 351 try { 352 encodeResourceToWriter(theResource, stringWriter); 353 } catch (IOException e) { 354 throw new Error("Encountered IOException during write to string - This should not happen!"); 355 } 356 return stringWriter.toString(); 357 } 358 359 @Override 360 public final void encodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws IOException, DataFormatException { 361 Validate.notNull(theResource, "theResource can not be null"); 362 Validate.notNull(theWriter, "theWriter can not be null"); 363 364 doEncodeResourceToWriter(theResource, theWriter); 365 } 366 367 @Override 368 public String encodeTagListToString(TagList theTagList) { 369 Writer stringWriter = new StringWriter(); 370 try { 371 encodeTagListToWriter(theTagList, stringWriter); 372 } catch (IOException e) { 373 throw new Error("Encountered IOException during write to string - This should not happen!"); 374 } 375 return stringWriter.toString(); 376 } 377 378 private void filterCodingsWithNoCodeOrSystem(List<? extends IBaseCoding> tagList) { 379 for (int i = 0; i < tagList.size(); i++) { 380 if (isBlank(tagList.get(i).getCode()) && isBlank(tagList.get(i).getSystem())) { 381 tagList.remove(i); 382 i--; 383 } 384 } 385 } 386 387 protected String fixContainedResourceId(String theValue) { 388 if (StringUtils.isNotBlank(theValue) && theValue.charAt(0) == '#') { 389 return theValue.substring(1); 390 } 391 return theValue; 392 } 393 394 ContainedResources getContainedResources() { 395 return myContainedResources; 396 } 397 398 /** 399 * See {@link #setEncodeElements(Set)} 400 */ 401 @Override 402 public Set<String> getEncodeElements() { 403 return myEncodeElements; 404 } 405 406 /** 407 * See {@link #setEncodeElementsAppliesToResourceTypes(Set)} 408 */ 409 @Override 410 public Set<String> getEncodeElementsAppliesToResourceTypes() { 411 return myEncodeElementsAppliesToResourceTypes; 412 } 413 414 protected IParserErrorHandler getErrorHandler() { 415 return myErrorHandler; 416 } 417 418 protected TagList getMetaTagsForEncoding(IResource theIResource) { 419 TagList tags = ResourceMetadataKeyEnum.TAG_LIST.get(theIResource); 420 if (shouldAddSubsettedTag()) { 421 tags = new TagList(tags); 422 tags.add(new Tag(Constants.TAG_SUBSETTED_SYSTEM, Constants.TAG_SUBSETTED_CODE, subsetDescription())); 423 } 424 425 return tags; 426 } 427 428 /** 429 * If set to <code>true</code> (default is <code>false</code>), narratives will not be included in the encoded 430 * values. 431 * 432 * @deprecated Use {@link #isSuppressNarratives()} 433 */ 434 @Deprecated 435 public boolean getSuppressNarratives() { 436 return mySuppressNarratives; 437 } 438 439 protected boolean isChildContained(BaseRuntimeElementDefinition<?> childDef, boolean theIncludedResource) { 440 return (childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCES || childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST) && getContainedResources().isEmpty() == false && theIncludedResource == false; 441 } 442 443 @Override 444 public boolean isOmitResourceId() { 445 return myOmitResourceId; 446 } 447 448 @Override 449 public boolean isStripVersionsFromReferences() { 450 return myStripVersionsFromReferences; 451 } 452 453 @Override 454 public boolean isSummaryMode() { 455 return mySummaryMode; 456 } 457 458 /** 459 * If set to <code>true</code> (default is <code>false</code>), narratives will not be included in the encoded 460 * values. 461 * 462 * @since 1.2 463 */ 464 public boolean isSuppressNarratives() { 465 return mySuppressNarratives; 466 } 467 468 @Override 469 public Bundle parseBundle(Reader theReader) { 470 if (myContext.getVersion().getVersion() == FhirVersionEnum.DSTU2_HL7ORG) { 471 throw new IllegalStateException("Can't parse DSTU1 (Atom) bundle in HL7.org DSTU2 mode. Use parseResource(Bundle.class, foo) instead."); 472 } 473 return parseBundle(null, theReader); 474 } 475 476 @Override 477 public Bundle parseBundle(String theXml) throws ConfigurationException, DataFormatException { 478 StringReader reader = new StringReader(theXml); 479 return parseBundle(reader); 480 } 481 482 @Override 483 public <T extends IBaseResource> T parseResource(Class<T> theResourceType, Reader theReader) throws DataFormatException { 484 T retVal = doParseResource(theResourceType, theReader); 485 486 RuntimeResourceDefinition def = myContext.getResourceDefinition(retVal); 487 if ("Bundle".equals(def.getName())) { 488 489 BaseRuntimeChildDefinition entryChild = def.getChildByName("entry"); 490 BaseRuntimeElementCompositeDefinition<?> entryDef = (BaseRuntimeElementCompositeDefinition<?>) entryChild.getChildByName("entry"); 491 List<IBase> entries = entryChild.getAccessor().getValues(retVal); 492 if (entries != null) { 493 for (IBase nextEntry : entries) { 494 495 /** 496 * If Bundle.entry.fullUrl is populated, set the resource ID to that 497 */ 498 // TODO: should emit a warning and maybe notify the error handler if the resource ID doesn't match the 499 // fullUrl idPart 500 BaseRuntimeChildDefinition fullUrlChild = entryDef.getChildByName("fullUrl"); 501 if (fullUrlChild == null) { 502 continue; // TODO: remove this once the data model in tinder plugin catches up to 1.2 503 } 504 List<IBase> fullUrl = fullUrlChild.getAccessor().getValues(nextEntry); 505 if (fullUrl != null && !fullUrl.isEmpty()) { 506 IPrimitiveType<?> value = (IPrimitiveType<?>) fullUrl.get(0); 507 if (value.isEmpty() == false) { 508 List<IBase> entryResources = entryDef.getChildByName("resource").getAccessor().getValues(nextEntry); 509 if (entryResources != null && entryResources.size() > 0) { 510 IBaseResource res = (IBaseResource) entryResources.get(0); 511 String versionId = res.getIdElement().getVersionIdPart(); 512 res.setId(value.getValueAsString()); 513 if (isNotBlank(versionId) && res.getIdElement().hasVersionIdPart() == false) { 514 res.setId(res.getIdElement().withVersion(versionId)); 515 } 516 } 517 } 518 } 519 520 } 521 } 522 523 } 524 525 return retVal; 526 } 527 528 @SuppressWarnings("cast") 529 @Override 530 public <T extends IBaseResource> T parseResource(Class<T> theResourceType, String theMessageString) { 531 StringReader reader = new StringReader(theMessageString); 532 return (T) parseResource(theResourceType, reader); 533 } 534 535 @Override 536 public IBaseResource parseResource(Reader theReader) throws ConfigurationException, DataFormatException { 537 return parseResource(null, theReader); 538 } 539 540 @Override 541 public IBaseResource parseResource(String theMessageString) throws ConfigurationException, DataFormatException { 542 return parseResource(null, theMessageString); 543 } 544 545 @Override 546 public TagList parseTagList(String theString) { 547 return parseTagList(new StringReader(theString)); 548 } 549 550 @SuppressWarnings("cast") 551 protected List<? extends IBase> preProcessValues(BaseRuntimeChildDefinition metaChildUncast, IBaseResource theResource, List<? extends IBase> theValues) { 552 if (myContext.getVersion().getVersion().isRi()) { 553 554 /* 555 * If we're encoding the meta tag, we do some massaging of the meta values before 556 * encoding. Buf if there is no meta element at all, we create one since we're possibly going to be 557 * adding things to it 558 */ 559 if (theValues.isEmpty() && metaChildUncast.getElementName().equals("meta")) { 560 BaseRuntimeElementDefinition<?> metaChild = metaChildUncast.getChildByName("meta"); 561 if (IBaseMetaType.class.isAssignableFrom(metaChild.getImplementingClass())) { 562 IBaseMetaType newType = (IBaseMetaType) metaChild.newInstance(); 563 theValues = Collections.singletonList(newType); 564 } 565 } 566 567 if (theValues.size() == 1 && theValues.get(0) instanceof IBaseMetaType) { 568 569 IBaseMetaType metaValue = (IBaseMetaType) theValues.get(0); 570 try { 571 metaValue = (IBaseMetaType) metaValue.getClass().getMethod("copy").invoke(metaValue); 572 } catch (Exception e) { 573 throw new InternalErrorException("Failed to duplicate meta", e); 574 } 575 576 if (isBlank(metaValue.getVersionId())) { 577 if (theResource.getIdElement().hasVersionIdPart()) { 578 metaValue.setVersionId(theResource.getIdElement().getVersionIdPart()); 579 } 580 } 581 582 filterCodingsWithNoCodeOrSystem(metaValue.getTag()); 583 filterCodingsWithNoCodeOrSystem(metaValue.getSecurity()); 584 585 if (shouldAddSubsettedTag()) { 586 IBaseCoding coding = metaValue.addTag(); 587 coding.setCode(Constants.TAG_SUBSETTED_CODE); 588 coding.setSystem(Constants.TAG_SUBSETTED_SYSTEM); 589 coding.setDisplay(subsetDescription()); 590 } 591 592 return Collections.singletonList(metaValue); 593 } 594 } 595 596 return theValues; 597 } 598 599 @Override 600 public void setEncodeElements(Set<String> theEncodeElements) { 601 myEncodeElementsIncludesStars = false; 602 if (theEncodeElements == null || theEncodeElements.isEmpty()) { 603 myEncodeElements = null; 604 } else { 605 myEncodeElements = theEncodeElements; 606 for (String next : theEncodeElements) { 607 if (next.startsWith("*.")) { 608 myEncodeElementsIncludesStars = true; 609 } 610 } 611 } 612 } 613 614 @Override 615 public void setEncodeElementsAppliesToResourceTypes(Set<String> theEncodeElementsAppliesToResourceTypes) { 616 if (theEncodeElementsAppliesToResourceTypes == null || theEncodeElementsAppliesToResourceTypes.isEmpty()) { 617 myEncodeElementsAppliesToResourceTypes = null; 618 } else { 619 myEncodeElementsAppliesToResourceTypes = theEncodeElementsAppliesToResourceTypes; 620 } 621 } 622 623 @Override 624 public IParser setOmitResourceId(boolean theOmitResourceId) { 625 myOmitResourceId = theOmitResourceId; 626 return this; 627 } 628 629 @Override 630 public IParser setParserErrorHandler(IParserErrorHandler theErrorHandler) { 631 Validate.notNull(theErrorHandler, "theErrorHandler must not be null"); 632 myErrorHandler = theErrorHandler; 633 return this; 634 } 635 636 @Override 637 public IParser setServerBaseUrl(String theUrl) { 638 myServerBaseUrl = isNotBlank(theUrl) ? theUrl : null; 639 return this; 640 } 641 642 @Override 643 public IParser setStripVersionsFromReferences(boolean theStripVersionsFromReferences) { 644 myStripVersionsFromReferences = theStripVersionsFromReferences; 645 return this; 646 } 647 648 @Override 649 public IParser setSummaryMode(boolean theSummaryMode) { 650 mySummaryMode = theSummaryMode; 651 return this; 652 } 653 654 @Override 655 public IParser setSuppressNarratives(boolean theSuppressNarratives) { 656 mySuppressNarratives = theSuppressNarratives; 657 return this; 658 } 659 660 protected boolean shouldAddSubsettedTag() { 661 return isSummaryMode() || isSuppressNarratives(); 662 } 663 664 private String subsetDescription() { 665 return "Resource encoded in summary mode"; 666 } 667 668 protected void throwExceptionForUnknownChildType(BaseRuntimeChildDefinition nextChild, Class<? extends IBase> theType) { 669 if (nextChild instanceof BaseRuntimeDeclaredChildDefinition) { 670 StringBuilder b = new StringBuilder(); 671 b.append(((BaseRuntimeDeclaredChildDefinition) nextChild).getElementName()); 672 b.append(" has type "); 673 b.append(theType.getName()); 674 b.append(" but this is not a valid type for this element"); 675 if (nextChild instanceof RuntimeChildChoiceDefinition) { 676 RuntimeChildChoiceDefinition choice = (RuntimeChildChoiceDefinition) nextChild; 677 b.append(" - Expected one of: " + choice.getValidChildTypes()); 678 } 679 throw new DataFormatException(b.toString()); 680 } 681 throw new DataFormatException(nextChild + " has no child of type " + theType); 682 } 683 684 protected static <T> List<T> extractMetadataListNotNull(IResource resource, ResourceMetadataKeyEnum<List<T>> key) { 685 List<T> securityLabels = key.get(resource); 686 if (securityLabels == null) { 687 securityLabels = Collections.emptyList(); 688 } 689 return securityLabels; 690 } 691 692 protected class CompositeChildElement { 693 private final BaseRuntimeChildDefinition myDef; 694 private final CompositeChildElement myParent; 695 private final RuntimeResourceDefinition myResDef; 696 697 public CompositeChildElement(CompositeChildElement theParent, BaseRuntimeChildDefinition theDef) { 698 myDef = theDef; 699 myParent = theParent; 700 myResDef = null; 701 702 if (ourLog.isTraceEnabled()) { 703 if (theParent != null) { 704 StringBuilder path = theParent.buildPath(); 705 if (path != null) { 706 path.append('.'); 707 path.append(myDef.getElementName()); 708 ourLog.trace(" * Next path: {}", path.toString()); 709 } 710 } 711 } 712 713 } 714 715 716 public CompositeChildElement(RuntimeResourceDefinition theResDef) { 717 myResDef = theResDef; 718 myDef = null; 719 myParent = null; 720 } 721 722 private StringBuilder buildPath() { 723 if (myResDef != null) { 724 StringBuilder b = new StringBuilder(); 725 b.append(myResDef.getName()); 726 return b; 727 } else { 728 StringBuilder b = myParent.buildPath(); 729 if (b != null && myDef != null) { 730 b.append('.'); 731 b.append(myDef.getElementName()); 732 } 733 return b; 734 } 735 } 736 737 private boolean checkIfParentShouldBeEncodedAndBuildPath(StringBuilder theB, boolean theStarPass) { 738 if (myResDef != null) { 739 if (myEncodeElementsAppliesToResourceTypes != null) { 740 if (!myEncodeElementsAppliesToResourceTypes.contains(myResDef.getName())) { 741 return true; 742 } 743 } 744 if (theStarPass) { 745 theB.append('*'); 746 } else { 747 theB.append(myResDef.getName()); 748 } 749 if (myEncodeElements.contains(theB.toString())) { 750 return true; 751 } else { 752 return false; 753 } 754 } else if (myParent != null) { 755 if (myParent.checkIfParentShouldBeEncodedAndBuildPath(theB, theStarPass)) { 756 return true; 757 } 758 759 if (myDef != null) { 760 theB.append('.'); 761 theB.append(myDef.getElementName()); 762 return myEncodeElements.contains(theB.toString()); 763 } 764 } 765 766 return true; 767 } 768 769 public BaseRuntimeChildDefinition getDef() { 770 return myDef; 771 } 772 773 public CompositeChildElement getParent() { 774 return myParent; 775 } 776 777 public RuntimeResourceDefinition getResDef() { 778 return myResDef; 779 } 780 781 public boolean shouldBeEncoded() { 782 if (myEncodeElements == null) { 783 return true; 784 } 785 boolean retVal = checkIfParentShouldBeEncodedAndBuildPath(new StringBuilder(), false); 786 if (retVal == false && myEncodeElementsIncludesStars) { 787 retVal = checkIfParentShouldBeEncodedAndBuildPath(new StringBuilder(), true); 788 } 789 return retVal; 790 } 791 } 792 793 static class ContainedResources { 794 private long myNextContainedId = 1; 795 796 private List<IBaseResource> myResources = new ArrayList<IBaseResource>(); 797 private IdentityHashMap<IBaseResource, IIdType> myResourceToId = new IdentityHashMap<IBaseResource, IIdType>(); 798 799 public void addContained(IBaseResource theResource) { 800 if (myResourceToId.containsKey(theResource)) { 801 return; 802 } 803 804 IIdType newId; 805 if (theResource.getIdElement().isLocal()) { 806 newId = theResource.getIdElement(); 807 } else { 808 // TODO: make this configurable between the two below (and something else?) 809 // newId = new IdDt(UUID.randomUUID().toString()); 810 newId = new IdDt(myNextContainedId++); 811 } 812 813 myResourceToId.put(theResource, newId); 814 myResources.add(theResource); 815 } 816 817 public void addContained(IIdType theId, IBaseResource theResource) { 818 myResourceToId.put(theResource, theId); 819 myResources.add(theResource); 820 } 821 822 public List<IBaseResource> getContainedResources() { 823 return myResources; 824 } 825 826 public IIdType getResourceId(IBaseResource theNext) { 827 return myResourceToId.get(theNext); 828 } 829 830 public boolean isEmpty() { 831 return myResourceToId.isEmpty(); 832 } 833 834 } 835 836}