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