001package ca.uhn.fhir.parser; 002 003/* 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2019 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 com.google.common.base.Charsets; 031import org.apache.commons.lang3.StringUtils; 032import org.apache.commons.lang3.Validate; 033import org.apache.commons.lang3.builder.EqualsBuilder; 034import org.apache.commons.lang3.builder.HashCodeBuilder; 035import org.hl7.fhir.instance.model.api.*; 036 037import java.io.*; 038import java.lang.reflect.Modifier; 039import java.util.*; 040import java.util.stream.Collectors; 041 042import static org.apache.commons.lang3.StringUtils.isBlank; 043import static org.apache.commons.lang3.StringUtils.isNotBlank; 044 045@SuppressWarnings("WeakerAccess") 046public abstract class BaseParser implements IParser { 047 048 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseParser.class); 049 050 private static final Set<String> notEncodeForContainedResource = new HashSet<>(Arrays.asList("security", "versionId", "lastUpdated")); 051 052 private ContainedResources myContainedResources; 053 private boolean myEncodeElementsAppliesToChildResourcesOnly; 054 private FhirContext myContext; 055 private List<ElementsPath> myDontEncodeElements; 056 private List<ElementsPath> myEncodeElements; 057 private Set<String> myEncodeElementsAppliesToResourceTypes; 058 private IIdType myEncodeForceResourceId; 059 private IParserErrorHandler myErrorHandler; 060 private boolean myOmitResourceId; 061 private List<Class<? extends IBaseResource>> myPreferTypes; 062 private String myServerBaseUrl; 063 private Boolean myStripVersionsFromReferences; 064 private Boolean myOverrideResourceIdWithBundleEntryFullUrl; 065 private boolean mySummaryMode; 066 private boolean mySuppressNarratives; 067 private Set<String> myDontStripVersionsFromReferencesAtPaths; 068 /** 069 * Constructor 070 */ 071 public BaseParser(FhirContext theContext, IParserErrorHandler theParserErrorHandler) { 072 myContext = theContext; 073 myErrorHandler = theParserErrorHandler; 074 } 075 076 List<ElementsPath> getDontEncodeElements() { 077 return myDontEncodeElements; 078 } 079 080 @Override 081 public void setDontEncodeElements(Set<String> theDontEncodeElements) { 082 if (theDontEncodeElements == null || theDontEncodeElements.isEmpty()) { 083 myDontEncodeElements = null; 084 } else { 085 myDontEncodeElements = theDontEncodeElements 086 .stream() 087 .map(ElementsPath::new) 088 .collect(Collectors.toList()); 089 } 090 } 091 092 List<ElementsPath> getEncodeElements() { 093 return myEncodeElements; 094 } 095 096 @Override 097 public void setEncodeElements(Set<String> theEncodeElements) { 098 099 if (theEncodeElements == null || theEncodeElements.isEmpty()) { 100 myEncodeElements = null; 101 myEncodeElementsAppliesToResourceTypes = null; 102 } else { 103 myEncodeElements = theEncodeElements 104 .stream() 105 .map(ElementsPath::new) 106 .collect(Collectors.toList()); 107 108 myEncodeElementsAppliesToResourceTypes = new HashSet<>(); 109 for (String next : myEncodeElements.stream().map(t -> t.getPath().get(0).getName()).collect(Collectors.toList())) { 110 if (next.startsWith("*")) { 111 myEncodeElementsAppliesToResourceTypes = null; 112 break; 113 } 114 int dotIdx = next.indexOf('.'); 115 if (dotIdx == -1) { 116 myEncodeElementsAppliesToResourceTypes.add(next); 117 } else { 118 myEncodeElementsAppliesToResourceTypes.add(next.substring(0, dotIdx)); 119 } 120 } 121 122 } 123 } 124 125 protected Iterable<CompositeChildElement> compositeChildIterator(IBase theCompositeElement, final boolean theContainedResource, final CompositeChildElement theParent, EncodeContext theEncodeContext) { 126 127 BaseRuntimeElementCompositeDefinition<?> elementDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theCompositeElement.getClass()); 128 final List<BaseRuntimeChildDefinition> children = elementDef.getChildrenAndExtension(); 129 130 return new Iterable<BaseParser.CompositeChildElement>() { 131 132 @Override 133 public Iterator<CompositeChildElement> iterator() { 134 135 return new Iterator<CompositeChildElement>() { 136 private Iterator<? extends BaseRuntimeChildDefinition> myChildrenIter; 137 private Boolean myHasNext = null; 138 private CompositeChildElement myNext; 139 140 /** 141 * Constructor 142 */ { 143 myChildrenIter = children.iterator(); 144 } 145 146 @Override 147 public boolean hasNext() { 148 if (myHasNext != null) { 149 return myHasNext; 150 } 151 152 myNext = null; 153 do { 154 if (myChildrenIter.hasNext() == false) { 155 myHasNext = Boolean.FALSE; 156 return false; 157 } 158 159 myNext = new CompositeChildElement(theParent, myChildrenIter.next(), theEncodeContext); 160 161 /* 162 * There are lots of reasons we might skip encoding a particular child 163 */ 164 if (myNext.getDef().getElementName().equals("id")) { 165 myNext = null; 166 } else if (!myNext.shouldBeEncoded(theContainedResource)) { 167 myNext = null; 168 } else if (isSummaryMode() && !myNext.getDef().isSummary()) { 169 myNext = null; 170 } else if (myNext.getDef() instanceof RuntimeChildNarrativeDefinition) { 171 if (isSuppressNarratives() || isSummaryMode()) { 172 myNext = null; 173 } else if (theContainedResource) { 174 myNext = null; 175 } 176 } else if (myNext.getDef() instanceof RuntimeChildContainedResources) { 177 if (theContainedResource) { 178 myNext = null; 179 } 180 } 181 } while (myNext == null); 182 183 myHasNext = true; 184 return true; 185 } 186 187 @Override 188 public CompositeChildElement next() { 189 if (myHasNext == null) { 190 if (!hasNext()) { 191 throw new IllegalStateException(); 192 } 193 } 194 CompositeChildElement retVal = myNext; 195 myNext = null; 196 myHasNext = null; 197 return retVal; 198 } 199 200 @Override 201 public void remove() { 202 throw new UnsupportedOperationException(); 203 } 204 }; 205 } 206 }; 207 } 208 209 private void containResourcesForEncoding(ContainedResources theContained, IBaseResource theResource, IBaseResource theTarget) { 210 211 if (theTarget instanceof IResource) { 212 List<? extends IResource> containedResources = ((IResource) theTarget).getContained().getContainedResources(); 213 for (IResource next : containedResources) { 214 String nextId = next.getId().getValue(); 215 if (StringUtils.isNotBlank(nextId)) { 216 if (!nextId.startsWith("#")) { 217 nextId = '#' + nextId; 218 } 219 theContained.getExistingIdToContainedResource().put(nextId, next); 220 } 221 } 222 } else if (theTarget instanceof IDomainResource) { 223 List<? extends IAnyResource> containedResources = ((IDomainResource) theTarget).getContained(); 224 for (IAnyResource next : containedResources) { 225 String nextId = next.getIdElement().getValue(); 226 if (StringUtils.isNotBlank(nextId)) { 227 if (!nextId.startsWith("#")) { 228 nextId = '#' + nextId; 229 } 230 theContained.getExistingIdToContainedResource().put(nextId, next); 231 } 232 } 233 } 234 235 List<IBaseReference> allReferences = myContext.newTerser().getAllPopulatedChildElementsOfType(theResource, IBaseReference.class); 236 for (IBaseReference next : allReferences) { 237 IBaseResource resource = next.getResource(); 238 if (resource == null && next.getReferenceElement().isLocal()) { 239 if (theContained.hasExistingIdToContainedResource()) { 240 IBaseResource potentialTarget = theContained.getExistingIdToContainedResource().remove(next.getReferenceElement().getValue()); 241 if (potentialTarget != null) { 242 theContained.addContained(next.getReferenceElement(), potentialTarget); 243 containResourcesForEncoding(theContained, potentialTarget, theTarget); 244 } 245 } 246 } 247 } 248 249 for (IBaseReference next : allReferences) { 250 IBaseResource resource = next.getResource(); 251 if (resource != null) { 252 if (resource.getIdElement().isEmpty() || resource.getIdElement().isLocal()) { 253 if (theContained.getResourceId(resource) != null) { 254 // Prevent infinite recursion if there are circular loops in the contained resources 255 continue; 256 } 257 theContained.addContained(resource); 258 if (resource.getIdElement().isLocal() && theContained.hasExistingIdToContainedResource()) { 259 theContained.getExistingIdToContainedResource().remove(resource.getIdElement().getValue()); 260 } 261 } else { 262 continue; 263 } 264 265 containResourcesForEncoding(theContained, resource, theTarget); 266 } 267 268 } 269 270 } 271 272 protected void containResourcesForEncoding(IBaseResource theResource) { 273 ContainedResources contained = new ContainedResources(); 274 containResourcesForEncoding(contained, theResource, theResource); 275 contained.assignIdsToContainedResources(); 276 myContainedResources = contained; 277 278 } 279 280 private String determineReferenceText(IBaseReference theRef, CompositeChildElement theCompositeChildElement) { 281 IIdType ref = theRef.getReferenceElement(); 282 if (isBlank(ref.getIdPart())) { 283 String reference = ref.getValue(); 284 if (theRef.getResource() != null) { 285 IIdType containedId = getContainedResources().getResourceId(theRef.getResource()); 286 if (containedId != null && !containedId.isEmpty()) { 287 if (containedId.isLocal()) { 288 reference = containedId.getValue(); 289 } else { 290 reference = "#" + containedId.getValue(); 291 } 292 } else { 293 IIdType refId = theRef.getResource().getIdElement(); 294 if (refId != null) { 295 if (refId.hasIdPart()) { 296 if (refId.getValue().startsWith("urn:")) { 297 reference = refId.getValue(); 298 } else { 299 if (!refId.hasResourceType()) { 300 refId = refId.withResourceType(myContext.getResourceDefinition(theRef.getResource()).getName()); 301 } 302 if (isStripVersionsFromReferences(theCompositeChildElement)) { 303 reference = refId.toVersionless().getValue(); 304 } else { 305 reference = refId.getValue(); 306 } 307 } 308 } 309 } 310 } 311 } 312 return reference; 313 } 314 if (!ref.hasResourceType() && !ref.isLocal() && theRef.getResource() != null) { 315 ref = ref.withResourceType(myContext.getResourceDefinition(theRef.getResource()).getName()); 316 } 317 if (isNotBlank(myServerBaseUrl) && StringUtils.equals(myServerBaseUrl, ref.getBaseUrl())) { 318 if (isStripVersionsFromReferences(theCompositeChildElement)) { 319 return ref.toUnqualifiedVersionless().getValue(); 320 } 321 return ref.toUnqualified().getValue(); 322 } 323 if (isStripVersionsFromReferences(theCompositeChildElement)) { 324 return ref.toVersionless().getValue(); 325 } 326 return ref.getValue(); 327 } 328 329 protected abstract void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter, EncodeContext theEncodeContext) throws IOException, DataFormatException; 330 331 protected abstract <T extends IBaseResource> T doParseResource(Class<T> theResourceType, Reader theReader) throws DataFormatException; 332 333 @Override 334 public String encodeResourceToString(IBaseResource theResource) throws DataFormatException { 335 Writer stringWriter = new StringWriter(); 336 try { 337 encodeResourceToWriter(theResource, stringWriter); 338 } catch (IOException e) { 339 throw new Error("Encountered IOException during write to string - This should not happen!"); 340 } 341 return stringWriter.toString(); 342 } 343 344 @Override 345 public final void encodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws IOException, DataFormatException { 346 EncodeContext encodeContext = new EncodeContext(); 347 348 encodeResourceToWriter(theResource, theWriter, encodeContext); 349 } 350 351 protected void encodeResourceToWriter(IBaseResource theResource, Writer theWriter, EncodeContext theEncodeContext) throws IOException { 352 Validate.notNull(theResource, "theResource can not be null"); 353 Validate.notNull(theWriter, "theWriter can not be null"); 354 Validate.notNull(theEncodeContext, "theEncodeContext can not be null"); 355 356 if (theResource.getStructureFhirVersionEnum() != myContext.getVersion().getVersion()) { 357 throw new IllegalArgumentException( 358 "This parser is for FHIR version " + myContext.getVersion().getVersion() + " - Can not encode a structure for version " + theResource.getStructureFhirVersionEnum()); 359 } 360 361 String resourceName = myContext.getResourceDefinition(theResource).getName(); 362 theEncodeContext.pushPath(resourceName, true); 363 364 doEncodeResourceToWriter(theResource, theWriter, theEncodeContext); 365 366 theEncodeContext.popPath(); 367 } 368 369 private void filterCodingsWithNoCodeOrSystem(List<? extends IBaseCoding> tagList) { 370 for (int i = 0; i < tagList.size(); i++) { 371 if (isBlank(tagList.get(i).getCode()) && isBlank(tagList.get(i).getSystem())) { 372 tagList.remove(i); 373 i--; 374 } 375 } 376 } 377 378 protected IIdType fixContainedResourceId(String theValue) { 379 IIdType retVal = (IIdType) myContext.getElementDefinition("id").newInstance(); 380 if (StringUtils.isNotBlank(theValue) && theValue.charAt(0) == '#') { 381 retVal.setValue(theValue.substring(1)); 382 } else { 383 retVal.setValue(theValue); 384 } 385 return retVal; 386 } 387 388 @SuppressWarnings("unchecked") 389 ChildNameAndDef getChildNameAndDef(BaseRuntimeChildDefinition theChild, IBase theValue) { 390 Class<? extends IBase> type = theValue.getClass(); 391 String childName = theChild.getChildNameByDatatype(type); 392 BaseRuntimeElementDefinition<?> childDef = theChild.getChildElementDefinitionByDatatype(type); 393 if (childDef == null) { 394 // if (theValue instanceof IBaseExtension) { 395 // return null; 396 // } 397 398 /* 399 * For RI structures Enumeration class, this replaces the child def 400 * with the "code" one. This is messy, and presumably there is a better 401 * way.. 402 */ 403 BaseRuntimeElementDefinition<?> elementDef = myContext.getElementDefinition(type); 404 if (elementDef.getName().equals("code")) { 405 Class<? extends IBase> type2 = myContext.getElementDefinition("code").getImplementingClass(); 406 childDef = theChild.getChildElementDefinitionByDatatype(type2); 407 childName = theChild.getChildNameByDatatype(type2); 408 } 409 410 // See possibly the user has extended a built-in type without 411 // declaring it anywhere, as in XmlParserDstu3Test#testEncodeUndeclaredBlock 412 if (childDef == null) { 413 Class<?> nextSuperType = theValue.getClass(); 414 while (IBase.class.isAssignableFrom(nextSuperType) && childDef == null) { 415 if (Modifier.isAbstract(nextSuperType.getModifiers()) == false) { 416 BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition((Class<? extends IBase>) nextSuperType); 417 Class<?> nextChildType = def.getImplementingClass(); 418 childDef = theChild.getChildElementDefinitionByDatatype((Class<? extends IBase>) nextChildType); 419 childName = theChild.getChildNameByDatatype((Class<? extends IBase>) nextChildType); 420 } 421 nextSuperType = nextSuperType.getSuperclass(); 422 } 423 } 424 425 if (childDef == null) { 426 throwExceptionForUnknownChildType(theChild, type); 427 } 428 } 429 430 return new ChildNameAndDef(childName, childDef); 431 } 432 433 protected String getCompositeElementId(IBase theElement) { 434 String elementId = null; 435 if (!(theElement instanceof IBaseResource)) { 436 if (theElement instanceof IBaseElement) { 437 elementId = ((IBaseElement) theElement).getId(); 438 } else if (theElement instanceof IIdentifiableElement) { 439 elementId = ((IIdentifiableElement) theElement).getElementSpecificId(); 440 } 441 } 442 return elementId; 443 } 444 445 ContainedResources getContainedResources() { 446 return myContainedResources; 447 } 448 449 @Override 450 public Set<String> getDontStripVersionsFromReferencesAtPaths() { 451 return myDontStripVersionsFromReferencesAtPaths; 452 } 453 454 @Override 455 public IIdType getEncodeForceResourceId() { 456 return myEncodeForceResourceId; 457 } 458 459 @Override 460 public BaseParser setEncodeForceResourceId(IIdType theEncodeForceResourceId) { 461 myEncodeForceResourceId = theEncodeForceResourceId; 462 return this; 463 } 464 465 protected IParserErrorHandler getErrorHandler() { 466 return myErrorHandler; 467 } 468 469 protected List<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> getExtensionMetadataKeys(IResource resource) { 470 List<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> extensionMetadataKeys = new ArrayList<>(); 471 for (Map.Entry<ResourceMetadataKeyEnum<?>, Object> entry : resource.getResourceMetadata().entrySet()) { 472 if (entry.getKey() instanceof ResourceMetadataKeyEnum.ExtensionResourceMetadataKey) { 473 extensionMetadataKeys.add(entry); 474 } 475 } 476 477 return extensionMetadataKeys; 478 } 479 480 protected String getExtensionUrl(final String extensionUrl) { 481 String url = extensionUrl; 482 if (StringUtils.isNotBlank(extensionUrl) && StringUtils.isNotBlank(myServerBaseUrl)) { 483 url = !UrlUtil.isValid(extensionUrl) && extensionUrl.startsWith("/") ? myServerBaseUrl + extensionUrl : extensionUrl; 484 } 485 return url; 486 } 487 488 protected TagList getMetaTagsForEncoding(IResource theIResource, EncodeContext theEncodeContext) { 489 TagList tags = ResourceMetadataKeyEnum.TAG_LIST.get(theIResource); 490 if (shouldAddSubsettedTag(theEncodeContext)) { 491 tags = new TagList(tags); 492 tags.add(new Tag(getSubsettedCodeSystem(), Constants.TAG_SUBSETTED_CODE, subsetDescription())); 493 } 494 495 return tags; 496 } 497 498 @Override 499 public Boolean getOverrideResourceIdWithBundleEntryFullUrl() { 500 return myOverrideResourceIdWithBundleEntryFullUrl; 501 } 502 503 @Override 504 public List<Class<? extends IBaseResource>> getPreferTypes() { 505 return myPreferTypes; 506 } 507 508 @Override 509 public void setPreferTypes(List<Class<? extends IBaseResource>> thePreferTypes) { 510 if (thePreferTypes != null) { 511 ArrayList<Class<? extends IBaseResource>> types = new ArrayList<>(); 512 for (Class<? extends IBaseResource> next : thePreferTypes) { 513 if (Modifier.isAbstract(next.getModifiers()) == false) { 514 types.add(next); 515 } 516 } 517 myPreferTypes = Collections.unmodifiableList(types); 518 } else { 519 myPreferTypes = thePreferTypes; 520 } 521 } 522 523 @SuppressWarnings("deprecation") 524 protected <T extends IPrimitiveType<String>> List<T> getProfileTagsForEncoding(IBaseResource theResource, List<T> theProfiles) { 525 switch (myContext.getAddProfileTagWhenEncoding()) { 526 case NEVER: 527 return theProfiles; 528 case ONLY_FOR_CUSTOM: 529 RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource); 530 if (resDef.isStandardType()) { 531 return theProfiles; 532 } 533 break; 534 case ALWAYS: 535 break; 536 } 537 538 RuntimeResourceDefinition nextDef = myContext.getResourceDefinition(theResource); 539 String profile = nextDef.getResourceProfile(myServerBaseUrl); 540 if (isNotBlank(profile)) { 541 for (T next : theProfiles) { 542 if (profile.equals(next.getValue())) { 543 return theProfiles; 544 } 545 } 546 547 List<T> newList = new ArrayList<>(theProfiles); 548 549 BaseRuntimeElementDefinition<?> idElement = myContext.getElementDefinition("id"); 550 @SuppressWarnings("unchecked") 551 T newId = (T) idElement.newInstance(); 552 newId.setValue(profile); 553 554 newList.add(newId); 555 return newList; 556 } 557 558 return theProfiles; 559 } 560 561 protected String getServerBaseUrl() { 562 return myServerBaseUrl; 563 } 564 565 @Override 566 public Boolean getStripVersionsFromReferences() { 567 return myStripVersionsFromReferences; 568 } 569 570 /** 571 * If set to <code>true</code> (default is <code>false</code>), narratives will not be included in the encoded 572 * values. 573 * 574 * @deprecated Use {@link #isSuppressNarratives()} 575 */ 576 @Deprecated 577 public boolean getSuppressNarratives() { 578 return mySuppressNarratives; 579 } 580 581 protected boolean isChildContained(BaseRuntimeElementDefinition<?> childDef, boolean theIncludedResource) { 582 return (childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCES || childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST) && getContainedResources().isEmpty() == false 583 && theIncludedResource == false; 584 } 585 586 @Override 587 public boolean isEncodeElementsAppliesToChildResourcesOnly() { 588 return myEncodeElementsAppliesToChildResourcesOnly; 589 } 590 591 @Override 592 public void setEncodeElementsAppliesToChildResourcesOnly(boolean theEncodeElementsAppliesToChildResourcesOnly) { 593 myEncodeElementsAppliesToChildResourcesOnly = theEncodeElementsAppliesToChildResourcesOnly; 594 } 595 596 @Override 597 public boolean isOmitResourceId() { 598 return myOmitResourceId; 599 } 600 601 private boolean isOverrideResourceIdWithBundleEntryFullUrl() { 602 Boolean overrideResourceIdWithBundleEntryFullUrl = myOverrideResourceIdWithBundleEntryFullUrl; 603 if (overrideResourceIdWithBundleEntryFullUrl != null) { 604 return overrideResourceIdWithBundleEntryFullUrl; 605 } 606 607 return myContext.getParserOptions().isOverrideResourceIdWithBundleEntryFullUrl(); 608 } 609 610 private boolean isStripVersionsFromReferences(CompositeChildElement theCompositeChildElement) { 611 Boolean stripVersionsFromReferences = myStripVersionsFromReferences; 612 if (stripVersionsFromReferences != null) { 613 return stripVersionsFromReferences; 614 } 615 616 if (myContext.getParserOptions().isStripVersionsFromReferences() == false) { 617 return false; 618 } 619 620 Set<String> dontStripVersionsFromReferencesAtPaths = myDontStripVersionsFromReferencesAtPaths; 621 if (dontStripVersionsFromReferencesAtPaths != null) { 622 if (dontStripVersionsFromReferencesAtPaths.isEmpty() == false && theCompositeChildElement.anyPathMatches(dontStripVersionsFromReferencesAtPaths)) { 623 return false; 624 } 625 } 626 627 dontStripVersionsFromReferencesAtPaths = myContext.getParserOptions().getDontStripVersionsFromReferencesAtPaths(); 628 return dontStripVersionsFromReferencesAtPaths.isEmpty() != false || !theCompositeChildElement.anyPathMatches(dontStripVersionsFromReferencesAtPaths); 629 } 630 631 @Override 632 public boolean isSummaryMode() { 633 return mySummaryMode; 634 } 635 636 /** 637 * If set to <code>true</code> (default is <code>false</code>), narratives will not be included in the encoded 638 * values. 639 * 640 * @since 1.2 641 */ 642 public boolean isSuppressNarratives() { 643 return mySuppressNarratives; 644 } 645 646 @Override 647 public IBaseResource parseResource(InputStream theInputStream) throws DataFormatException { 648 return parseResource(new InputStreamReader(theInputStream, Charsets.UTF_8)); 649 } 650 651 @Override 652 public <T extends IBaseResource> T parseResource(Class<T> theResourceType, InputStream theInputStream) throws DataFormatException { 653 return parseResource(theResourceType, new InputStreamReader(theInputStream, Constants.CHARSET_UTF8)); 654 } 655 656 @Override 657 public <T extends IBaseResource> T parseResource(Class<T> theResourceType, Reader theReader) throws DataFormatException { 658 659 /* 660 * We do this so that the context can verify that the structure is for 661 * the correct FHIR version 662 */ 663 if (theResourceType != null) { 664 myContext.getResourceDefinition(theResourceType); 665 } 666 667 // Actually do the parse 668 T retVal = doParseResource(theResourceType, theReader); 669 670 RuntimeResourceDefinition def = myContext.getResourceDefinition(retVal); 671 if ("Bundle".equals(def.getName())) { 672 673 BaseRuntimeChildDefinition entryChild = def.getChildByName("entry"); 674 BaseRuntimeElementCompositeDefinition<?> entryDef = (BaseRuntimeElementCompositeDefinition<?>) entryChild.getChildByName("entry"); 675 List<IBase> entries = entryChild.getAccessor().getValues(retVal); 676 if (entries != null) { 677 for (IBase nextEntry : entries) { 678 679 /** 680 * If Bundle.entry.fullUrl is populated, set the resource ID to that 681 */ 682 // TODO: should emit a warning and maybe notify the error handler if the resource ID doesn't match the 683 // fullUrl idPart 684 BaseRuntimeChildDefinition fullUrlChild = entryDef.getChildByName("fullUrl"); 685 if (fullUrlChild == null) { 686 continue; // TODO: remove this once the data model in tinder plugin catches up to 1.2 687 } 688 if (isOverrideResourceIdWithBundleEntryFullUrl()) { 689 List<IBase> fullUrl = fullUrlChild.getAccessor().getValues(nextEntry); 690 if (fullUrl != null && !fullUrl.isEmpty()) { 691 IPrimitiveType<?> value = (IPrimitiveType<?>) fullUrl.get(0); 692 if (value.isEmpty() == false) { 693 List<IBase> entryResources = entryDef.getChildByName("resource").getAccessor().getValues(nextEntry); 694 if (entryResources != null && entryResources.size() > 0) { 695 IBaseResource res = (IBaseResource) entryResources.get(0); 696 String versionId = res.getIdElement().getVersionIdPart(); 697 res.setId(value.getValueAsString()); 698 if (isNotBlank(versionId) && res.getIdElement().hasVersionIdPart() == false) { 699 res.setId(res.getIdElement().withVersion(versionId)); 700 } 701 } 702 } 703 } 704 } 705 } 706 } 707 708 } 709 710 return retVal; 711 } 712 713 @SuppressWarnings("cast") 714 @Override 715 public <T extends IBaseResource> T parseResource(Class<T> theResourceType, String theMessageString) { 716 StringReader reader = new StringReader(theMessageString); 717 return parseResource(theResourceType, reader); 718 } 719 720 @Override 721 public IBaseResource parseResource(Reader theReader) throws ConfigurationException, DataFormatException { 722 return parseResource(null, theReader); 723 } 724 725 @Override 726 public IBaseResource parseResource(String theMessageString) throws ConfigurationException, DataFormatException { 727 return parseResource(null, theMessageString); 728 } 729 730 protected List<? extends IBase> preProcessValues(BaseRuntimeChildDefinition theMetaChildUncast, IBaseResource theResource, List<? extends IBase> theValues, 731 CompositeChildElement theCompositeChildElement, EncodeContext theEncodeContext) { 732 if (myContext.getVersion().getVersion().isRi()) { 733 734 /* 735 * If we're encoding the meta tag, we do some massaging of the meta values before 736 * encoding. But if there is no meta element at all, we create one since we're possibly going to be 737 * adding things to it 738 */ 739 if (theValues.isEmpty() && theMetaChildUncast.getElementName().equals("meta")) { 740 BaseRuntimeElementDefinition<?> metaChild = theMetaChildUncast.getChildByName("meta"); 741 if (IBaseMetaType.class.isAssignableFrom(metaChild.getImplementingClass())) { 742 IBaseMetaType newType = (IBaseMetaType) metaChild.newInstance(); 743 theValues = Collections.singletonList(newType); 744 } 745 } 746 747 if (theValues.size() == 1 && theValues.get(0) instanceof IBaseMetaType) { 748 749 IBaseMetaType metaValue = (IBaseMetaType) theValues.get(0); 750 try { 751 metaValue = (IBaseMetaType) metaValue.getClass().getMethod("copy").invoke(metaValue); 752 } catch (Exception e) { 753 throw new InternalErrorException("Failed to duplicate meta", e); 754 } 755 756 if (isBlank(metaValue.getVersionId())) { 757 if (theResource.getIdElement().hasVersionIdPart()) { 758 metaValue.setVersionId(theResource.getIdElement().getVersionIdPart()); 759 } 760 } 761 762 filterCodingsWithNoCodeOrSystem(metaValue.getTag()); 763 filterCodingsWithNoCodeOrSystem(metaValue.getSecurity()); 764 765 List<? extends IPrimitiveType<String>> newProfileList = getProfileTagsForEncoding(theResource, metaValue.getProfile()); 766 List<? extends IPrimitiveType<String>> oldProfileList = metaValue.getProfile(); 767 if (oldProfileList != newProfileList) { 768 oldProfileList.clear(); 769 for (IPrimitiveType<String> next : newProfileList) { 770 if (isNotBlank(next.getValue())) { 771 metaValue.addProfile(next.getValue()); 772 } 773 } 774 } 775 776 if (shouldAddSubsettedTag(theEncodeContext)) { 777 IBaseCoding coding = metaValue.addTag(); 778 coding.setCode(Constants.TAG_SUBSETTED_CODE); 779 coding.setSystem(getSubsettedCodeSystem()); 780 coding.setDisplay(subsetDescription()); 781 } 782 783 return Collections.singletonList(metaValue); 784 } 785 } 786 787 @SuppressWarnings("unchecked") 788 List<IBase> retVal = (List<IBase>) theValues; 789 790 for (int i = 0; i < retVal.size(); i++) { 791 IBase next = retVal.get(i); 792 793 /* 794 * If we have automatically contained any resources via 795 * their references, this ensures that we output the new 796 * local reference 797 */ 798 if (next instanceof IBaseReference) { 799 IBaseReference nextRef = (IBaseReference) next; 800 String refText = determineReferenceText(nextRef, theCompositeChildElement); 801 if (!StringUtils.equals(refText, nextRef.getReferenceElement().getValue())) { 802 803 if (retVal == theValues) { 804 retVal = new ArrayList<>(theValues); 805 } 806 IBaseReference newRef = (IBaseReference) myContext.getElementDefinition(nextRef.getClass()).newInstance(); 807 myContext.newTerser().cloneInto(nextRef, newRef, true); 808 newRef.setReference(refText); 809 retVal.set(i, newRef); 810 811 } 812 } 813 } 814 815 return retVal; 816 } 817 818 private String getSubsettedCodeSystem() { 819 if (myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) { 820 return Constants.TAG_SUBSETTED_SYSTEM_R4; 821 } else { 822 return Constants.TAG_SUBSETTED_SYSTEM_DSTU3; 823 } 824 } 825 826 @Override 827 public IParser setDontStripVersionsFromReferencesAtPaths(String... thePaths) { 828 if (thePaths == null) { 829 setDontStripVersionsFromReferencesAtPaths((List<String>) null); 830 } else { 831 setDontStripVersionsFromReferencesAtPaths(Arrays.asList(thePaths)); 832 } 833 return this; 834 } 835 836 @SuppressWarnings("unchecked") 837 @Override 838 public IParser setDontStripVersionsFromReferencesAtPaths(Collection<String> thePaths) { 839 if (thePaths == null) { 840 myDontStripVersionsFromReferencesAtPaths = Collections.emptySet(); 841 } else if (thePaths instanceof HashSet) { 842 myDontStripVersionsFromReferencesAtPaths = (Set<String>) ((HashSet<String>) thePaths).clone(); 843 } else { 844 myDontStripVersionsFromReferencesAtPaths = new HashSet<>(thePaths); 845 } 846 return this; 847 } 848 849 @Override 850 public IParser setOmitResourceId(boolean theOmitResourceId) { 851 myOmitResourceId = theOmitResourceId; 852 return this; 853 } 854 855 @Override 856 public IParser setOverrideResourceIdWithBundleEntryFullUrl(Boolean theOverrideResourceIdWithBundleEntryFullUrl) { 857 myOverrideResourceIdWithBundleEntryFullUrl = theOverrideResourceIdWithBundleEntryFullUrl; 858 return this; 859 } 860 861 @Override 862 public IParser setParserErrorHandler(IParserErrorHandler theErrorHandler) { 863 Validate.notNull(theErrorHandler, "theErrorHandler must not be null"); 864 myErrorHandler = theErrorHandler; 865 return this; 866 } 867 868 @Override 869 public IParser setServerBaseUrl(String theUrl) { 870 myServerBaseUrl = isNotBlank(theUrl) ? theUrl : null; 871 return this; 872 } 873 874 @Override 875 public IParser setStripVersionsFromReferences(Boolean theStripVersionsFromReferences) { 876 myStripVersionsFromReferences = theStripVersionsFromReferences; 877 return this; 878 } 879 880 @Override 881 public IParser setSummaryMode(boolean theSummaryMode) { 882 mySummaryMode = theSummaryMode; 883 return this; 884 } 885 886 @Override 887 public IParser setSuppressNarratives(boolean theSuppressNarratives) { 888 mySuppressNarratives = theSuppressNarratives; 889 return this; 890 } 891 892 protected boolean shouldAddSubsettedTag(EncodeContext theEncodeContext) { 893 if (isSummaryMode()) { 894 return true; 895 } 896 if (isSuppressNarratives()) { 897 return true; 898 } 899 if (myEncodeElements != null) { 900 if (isEncodeElementsAppliesToChildResourcesOnly() && theEncodeContext.getResourcePath().size() < 2) { 901 return false; 902 } 903 904 String currentResourceName = theEncodeContext.getResourcePath().get(theEncodeContext.getResourcePath().size() - 1).getName(); 905 if (myEncodeElementsAppliesToResourceTypes == null || myEncodeElementsAppliesToResourceTypes.contains(currentResourceName)) { 906 return true; 907 } 908 } 909 910 return false; 911 } 912 913 protected boolean shouldEncodeResourceId(IBaseResource theResource, EncodeContext theEncodeContext) { 914 boolean retVal = true; 915 if (isOmitResourceId()) { 916 retVal = false; 917 } else { 918 if (myDontEncodeElements != null) { 919 String resourceName = myContext.getResourceDefinition(theResource).getName(); 920 if (myDontEncodeElements.stream().anyMatch(t -> t.equalsPath(resourceName + ".id"))) { 921 retVal = false; 922 } else if (myDontEncodeElements.stream().anyMatch(t -> t.equalsPath("*.id"))) { 923 retVal = false; 924 } else if (theEncodeContext.getResourcePath().size() == 1 && myDontEncodeElements.stream().anyMatch(t -> t.equalsPath("id"))) { 925 retVal = false; 926 } 927 } 928 } 929 return retVal; 930 } 931 932 /** 933 * Used for DSTU2 only 934 */ 935 protected boolean shouldEncodeResourceMeta(IResource theResource) { 936 return shouldEncodePath(theResource, "meta"); 937 } 938 939 /** 940 * Used for DSTU2 only 941 */ 942 protected boolean shouldEncodePath(IResource theResource, String thePath) { 943 if (myDontEncodeElements != null) { 944 String resourceName = myContext.getResourceDefinition(theResource).getName(); 945 if (myDontEncodeElements.stream().anyMatch(t -> t.equalsPath(resourceName + "." + thePath))) { 946 return false; 947 } else if (myDontEncodeElements.stream().anyMatch(t -> t.equalsPath("*." + thePath))) { 948 return false; 949 } 950 } 951 return true; 952 } 953 954 private String subsetDescription() { 955 return "Resource encoded in summary mode"; 956 } 957 958 protected void throwExceptionForUnknownChildType(BaseRuntimeChildDefinition nextChild, Class<? extends IBase> theType) { 959 if (nextChild instanceof BaseRuntimeDeclaredChildDefinition) { 960 StringBuilder b = new StringBuilder(); 961 b.append(nextChild.getElementName()); 962 b.append(" has type "); 963 b.append(theType.getName()); 964 b.append(" but this is not a valid type for this element"); 965 if (nextChild instanceof RuntimeChildChoiceDefinition) { 966 RuntimeChildChoiceDefinition choice = (RuntimeChildChoiceDefinition) nextChild; 967 b.append(" - Expected one of: " + choice.getValidChildTypes()); 968 } 969 throw new DataFormatException(b.toString()); 970 } 971 throw new DataFormatException(nextChild + " has no child of type " + theType); 972 } 973 974 protected boolean shouldEncodeResource(String theName) { 975 if (myDontEncodeElements != null) { 976 for (ElementsPath next : myDontEncodeElements) { 977 if (next.equalsPath(theName)) { 978 return false; 979 } 980 } 981 } 982 return true; 983 } 984 985 class ChildNameAndDef { 986 987 private final BaseRuntimeElementDefinition<?> myChildDef; 988 private final String myChildName; 989 990 public ChildNameAndDef(String theChildName, BaseRuntimeElementDefinition<?> theChildDef) { 991 myChildName = theChildName; 992 myChildDef = theChildDef; 993 } 994 995 public BaseRuntimeElementDefinition<?> getChildDef() { 996 return myChildDef; 997 } 998 999 public String getChildName() { 1000 return myChildName; 1001 } 1002 1003 } 1004 1005 protected class CompositeChildElement { 1006 private final BaseRuntimeChildDefinition myDef; 1007 private final CompositeChildElement myParent; 1008 private final RuntimeResourceDefinition myResDef; 1009 private final EncodeContext myEncodeContext; 1010 1011 public CompositeChildElement(CompositeChildElement theParent, BaseRuntimeChildDefinition theDef, EncodeContext theEncodeContext) { 1012 myDef = theDef; 1013 myParent = theParent; 1014 myResDef = null; 1015 myEncodeContext = theEncodeContext; 1016 1017 if (ourLog.isTraceEnabled()) { 1018 if (theParent != null) { 1019 StringBuilder path = theParent.buildPath(); 1020 if (path != null) { 1021 path.append('.'); 1022 path.append(myDef.getElementName()); 1023 ourLog.trace(" * Next path: {}", path.toString()); 1024 } 1025 } 1026 } 1027 1028 } 1029 1030 public CompositeChildElement(RuntimeResourceDefinition theResDef, EncodeContext theEncodeContext) { 1031 myResDef = theResDef; 1032 myDef = null; 1033 myParent = null; 1034 myEncodeContext = theEncodeContext; 1035 } 1036 1037 private void addParent(CompositeChildElement theParent, StringBuilder theB) { 1038 if (theParent != null) { 1039 if (theParent.myResDef != null) { 1040 theB.append(theParent.myResDef.getName()); 1041 return; 1042 } 1043 1044 if (theParent.myParent != null) { 1045 addParent(theParent.myParent, theB); 1046 } 1047 1048 if (theParent.myDef != null) { 1049 if (theB.length() > 0) { 1050 theB.append('.'); 1051 } 1052 theB.append(theParent.myDef.getElementName()); 1053 } 1054 } 1055 } 1056 1057 public boolean anyPathMatches(Set<String> thePaths) { 1058 StringBuilder b = new StringBuilder(); 1059 addParent(this, b); 1060 1061 String path = b.toString(); 1062 return thePaths.contains(path); 1063 } 1064 1065 private StringBuilder buildPath() { 1066 if (myResDef != null) { 1067 StringBuilder b = new StringBuilder(); 1068 b.append(myResDef.getName()); 1069 return b; 1070 } else if (myParent != null) { 1071 StringBuilder b = myParent.buildPath(); 1072 if (b != null && myDef != null) { 1073 b.append('.'); 1074 b.append(myDef.getElementName()); 1075 } 1076 return b; 1077 } else { 1078 return null; 1079 } 1080 } 1081 1082 private boolean checkIfParentShouldBeEncodedAndBuildPath() { 1083 List<ElementsPath> encodeElements = myEncodeElements; 1084 1085 String currentResourceName = myEncodeContext.getResourcePath().get(myEncodeContext.getResourcePath().size() - 1).getName(); 1086 if (myEncodeElementsAppliesToResourceTypes != null && !myEncodeElementsAppliesToResourceTypes.contains(currentResourceName)) { 1087 encodeElements = null; 1088 } 1089 1090 boolean retVal = checkIfPathMatchesForEncoding(encodeElements, true); 1091 1092 /* 1093 * We force the meta tag to be encoded even if it's not specified as an element in the 1094 * elements filter, specifically because we'll need it in order to automatically add 1095 * the SUBSETTED tag 1096 */ 1097 if (!retVal) { 1098 if ("meta".equals(myEncodeContext.getLeafResourcePathFirstField()) && shouldAddSubsettedTag(myEncodeContext)) { 1099 // The next element is a child of the <meta> element 1100 retVal = true; 1101 } else if ("meta".equals(myDef.getElementName()) && shouldAddSubsettedTag(myEncodeContext)) { 1102 // The next element is the <meta> element 1103 retVal = true; 1104 } 1105 } 1106 1107 return retVal; 1108 } 1109 1110 private boolean checkIfParentShouldNotBeEncodedAndBuildPath() { 1111 return checkIfPathMatchesForEncoding(myDontEncodeElements, false); 1112 } 1113 1114 private boolean checkIfPathMatchesForEncoding(List<ElementsPath> theElements, boolean theCheckingForEncodeElements) { 1115 1116 boolean retVal = false; 1117 myEncodeContext.pushPath(myDef.getElementName(), false); 1118 1119 if (theCheckingForEncodeElements && isEncodeElementsAppliesToChildResourcesOnly() && myEncodeContext.getResourcePath().size() < 2) { 1120 retVal = true; 1121 } else if (theElements == null) { 1122 retVal = true; 1123 } else { 1124 EncodeContextPath currentResourcePath = myEncodeContext.getCurrentResourcePath(); 1125 ourLog.trace("Current resource path: {}", currentResourcePath); 1126 for (ElementsPath next : theElements) { 1127 1128 if (next.startsWith(currentResourcePath)) { 1129 if (theCheckingForEncodeElements || next.getPath().size() == currentResourcePath.getPath().size()) { 1130 retVal = true; 1131 break; 1132 } 1133 } 1134 1135 if (next.getPath().get(next.getPath().size() - 1).getName().equals("(mandatory)")) { 1136 if (myDef.getMin() > 0) { 1137 retVal = true; 1138 break; 1139 } 1140 if (currentResourcePath.getPath().size() > next.getPath().size()) { 1141 retVal = true; 1142 break; 1143 } 1144 } 1145 1146 } 1147 } 1148 1149 myEncodeContext.popPath(); 1150 return retVal; 1151 } 1152 1153 public BaseRuntimeChildDefinition getDef() { 1154 return myDef; 1155 } 1156 1157 public CompositeChildElement getParent() { 1158 return myParent; 1159 } 1160 1161 public boolean shouldBeEncoded(boolean theContainedResource) { 1162 boolean retVal = true; 1163 if (myEncodeElements != null) { 1164 retVal = checkIfParentShouldBeEncodedAndBuildPath(); 1165 } 1166 if (retVal && myDontEncodeElements != null) { 1167 retVal = !checkIfParentShouldNotBeEncodedAndBuildPath(); 1168 } 1169 if (theContainedResource) { 1170 retVal = !notEncodeForContainedResource.contains(myDef.getElementName()); 1171 } 1172 1173 return retVal; 1174 } 1175 } 1176 1177 protected class EncodeContextPath { 1178 private final List<EncodeContextPathElement> myPath; 1179 1180 public EncodeContextPath() { 1181 myPath = new ArrayList<>(10); 1182 } 1183 1184 public EncodeContextPath(List<EncodeContextPathElement> thePath) { 1185 myPath = thePath; 1186 } 1187 1188 @Override 1189 public String toString() { 1190 return myPath.toString(); 1191 } 1192 1193 protected List<EncodeContextPathElement> getPath() { 1194 return myPath; 1195 } 1196 1197 public EncodeContextPath getCurrentResourcePath() { 1198 EncodeContextPath retVal = null; 1199 for (int i = myPath.size() - 1; i >= 0; i--) { 1200 if (myPath.get(i).isResource()) { 1201 retVal = new EncodeContextPath(myPath.subList(i, myPath.size())); 1202 break; 1203 } 1204 } 1205 Validate.isTrue(retVal != null); 1206 return retVal; 1207 } 1208 } 1209 1210 protected class ElementsPath extends EncodeContextPath { 1211 1212 protected ElementsPath(String thePath) { 1213 StringTokenizer tok = new StringTokenizer(thePath, "."); 1214 boolean first = true; 1215 while (tok.hasMoreTokens()) { 1216 String next = tok.nextToken(); 1217 if (first && next.equals("*")) { 1218 getPath().add(new EncodeContextPathElement("*", true)); 1219 } else if (isNotBlank(next)) { 1220 getPath().add(new EncodeContextPathElement(next, Character.isUpperCase(next.charAt(0)))); 1221 } 1222 first = false; 1223 } 1224 } 1225 1226 public boolean startsWith(EncodeContextPath theCurrentResourcePath) { 1227 for (int i = 0; i < getPath().size(); i++) { 1228 if (theCurrentResourcePath.getPath().size() == i) { 1229 return true; 1230 } 1231 EncodeContextPathElement expected = getPath().get(i); 1232 EncodeContextPathElement actual = theCurrentResourcePath.getPath().get(i); 1233 if (!expected.matches(actual)) { 1234 return false; 1235 } 1236 } 1237 return true; 1238 } 1239 1240 public boolean equalsPath(String thePath) { 1241 ElementsPath parsedPath = new ElementsPath(thePath); 1242 return getPath().equals(parsedPath.getPath()); 1243 } 1244 } 1245 1246 1247 /** 1248 * EncodeContext is a shared state object that is passed around the 1249 * encode process 1250 */ 1251 protected class EncodeContext extends EncodeContextPath { 1252 private final ArrayList<EncodeContextPathElement> myResourcePath = new ArrayList<>(10); 1253 1254 protected ArrayList<EncodeContextPathElement> getResourcePath() { 1255 return myResourcePath; 1256 } 1257 1258 public String getLeafResourcePathFirstField() { 1259 String retVal = null; 1260 for (int i = getPath().size() - 1; i >= 0; i--) { 1261 if (getPath().get(i).isResource()) { 1262 break; 1263 } else { 1264 retVal = getPath().get(i).getName(); 1265 } 1266 } 1267 return retVal; 1268 } 1269 1270 1271 /** 1272 * Add an element at the end of the path 1273 */ 1274 protected void pushPath(String thePathElement, boolean theResource) { 1275 assert isNotBlank(thePathElement); 1276 assert !thePathElement.contains("."); 1277 assert theResource ^ Character.isLowerCase(thePathElement.charAt(0)); 1278 1279 EncodeContextPathElement element = new EncodeContextPathElement(thePathElement, theResource); 1280 getPath().add(element); 1281 if (theResource) { 1282 myResourcePath.add(element); 1283 } 1284 } 1285 1286 /** 1287 * Remove the element at the end of the path 1288 */ 1289 public void popPath() { 1290 EncodeContextPathElement removed = getPath().remove(getPath().size() - 1); 1291 if (removed.isResource()) { 1292 myResourcePath.remove(myResourcePath.size() - 1); 1293 } 1294 } 1295 1296 1297 } 1298 1299 protected class EncodeContextPathElement { 1300 private final String myName; 1301 private final boolean myResource; 1302 1303 public EncodeContextPathElement(String theName, boolean theResource) { 1304 Validate.notBlank(theName); 1305 myName = theName; 1306 myResource = theResource; 1307 } 1308 1309 1310 public boolean matches(EncodeContextPathElement theOther) { 1311 if (myResource != theOther.isResource()) { 1312 return false; 1313 } 1314 String otherName = theOther.getName(); 1315 if (myName.equals(otherName)) { 1316 return true; 1317 } 1318 /* 1319 * This is here to handle situations where a path like 1320 * Observation.valueQuantity has been specified as an include/exclude path, 1321 * since we only know that path as 1322 * Observation.value 1323 * until we get to actually looking at the values there. 1324 */ 1325 if (myName.length() > otherName.length() && myName.startsWith(otherName)) { 1326 char ch = myName.charAt(otherName.length()); 1327 if (Character.isUpperCase(ch)) { 1328 return true; 1329 } 1330 } 1331 if (myName.equals("*")) { 1332 return true; 1333 } 1334 return false; 1335 } 1336 1337 @Override 1338 public boolean equals(Object theO) { 1339 if (this == theO) { 1340 return true; 1341 } 1342 1343 if (theO == null || getClass() != theO.getClass()) { 1344 return false; 1345 } 1346 1347 EncodeContextPathElement that = (EncodeContextPathElement) theO; 1348 1349 return new EqualsBuilder() 1350 .append(myResource, that.myResource) 1351 .append(myName, that.myName) 1352 .isEquals(); 1353 } 1354 1355 @Override 1356 public int hashCode() { 1357 return new HashCodeBuilder(17, 37) 1358 .append(myName) 1359 .append(myResource) 1360 .toHashCode(); 1361 } 1362 1363 @Override 1364 public String toString() { 1365 if (myResource) { 1366 return myName + "(res)"; 1367 } 1368 return myName; 1369 } 1370 1371 public String getName() { 1372 return myName; 1373 } 1374 1375 public boolean isResource() { 1376 return myResource; 1377 } 1378 } 1379 1380 static class ContainedResources { 1381 private long myNextContainedId = 1; 1382 1383 private List<IBaseResource> myResourceList; 1384 private IdentityHashMap<IBaseResource, IIdType> myResourceToIdMap; 1385 private Map<String, IBaseResource> myExistingIdToContainedResourceMap; 1386 1387 public Map<String, IBaseResource> getExistingIdToContainedResource() { 1388 if (myExistingIdToContainedResourceMap == null) { 1389 myExistingIdToContainedResourceMap = new HashMap<>(); 1390 } 1391 return myExistingIdToContainedResourceMap; 1392 } 1393 1394 public void addContained(IBaseResource theResource) { 1395 if (getResourceToIdMap().containsKey(theResource)) { 1396 return; 1397 } 1398 1399 IIdType newId; 1400 if (theResource.getIdElement().isLocal()) { 1401 newId = theResource.getIdElement(); 1402 } else { 1403 newId = null; 1404 } 1405 1406 getResourceToIdMap().put(theResource, newId); 1407 getResourceList().add(theResource); 1408 } 1409 1410 public void addContained(IIdType theId, IBaseResource theResource) { 1411 if (!getResourceToIdMap().containsKey(theResource)) { 1412 getResourceToIdMap().put(theResource, theId); 1413 getResourceList().add(theResource); 1414 } 1415 } 1416 1417 public List<IBaseResource> getContainedResources() { 1418 if (getResourceToIdMap() == null) { 1419 return Collections.emptyList(); 1420 } 1421 return getResourceList(); 1422 } 1423 1424 public IIdType getResourceId(IBaseResource theNext) { 1425 if (getResourceToIdMap() == null) { 1426 return null; 1427 } 1428 return getResourceToIdMap().get(theNext); 1429 } 1430 1431 private List<IBaseResource> getResourceList() { 1432 if (myResourceList == null) { 1433 myResourceList = new ArrayList<>(); 1434 } 1435 return myResourceList; 1436 } 1437 1438 private IdentityHashMap<IBaseResource, IIdType> getResourceToIdMap() { 1439 if (myResourceToIdMap == null) { 1440 myResourceToIdMap = new IdentityHashMap<>(); 1441 } 1442 return myResourceToIdMap; 1443 } 1444 1445 public boolean isEmpty() { 1446 if (myResourceToIdMap == null) { 1447 return true; 1448 } 1449 return myResourceToIdMap.isEmpty(); 1450 } 1451 1452 public boolean hasExistingIdToContainedResource() { 1453 return myExistingIdToContainedResourceMap != null; 1454 } 1455 1456 public void assignIdsToContainedResources() { 1457 1458 if (getResourceList() != null) { 1459 1460 /* 1461 * The idea with the code block below: 1462 * 1463 * We want to preserve any IDs that were user-assigned, so that if it's really 1464 * important to someone that their contained resource have the ID of #FOO 1465 * or #1 we will keep that. 1466 * 1467 * For any contained resources where no ID was assigned by the user, we 1468 * want to manually create an ID but make sure we don't reuse an existing ID. 1469 */ 1470 1471 Set<String> ids = new HashSet<>(); 1472 1473 // Gather any user assigned IDs 1474 for (IBaseResource nextResource : getResourceList()) { 1475 if (getResourceToIdMap().get(nextResource) != null) { 1476 ids.add(getResourceToIdMap().get(nextResource).getValue()); 1477 } 1478 } 1479 1480 // Automatically assign IDs to the rest 1481 for (IBaseResource nextResource : getResourceList()) { 1482 1483 while (getResourceToIdMap().get(nextResource) == null) { 1484 String nextCandidate = "#" + myNextContainedId; 1485 myNextContainedId++; 1486 if (!ids.add(nextCandidate)) { 1487 continue; 1488 } 1489 1490 getResourceToIdMap().put(nextResource, new IdDt(nextCandidate)); 1491 } 1492 1493 } 1494 1495 } 1496 1497 } 1498 } 1499 1500 protected static <T> List<T> extractMetadataListNotNull(IResource resource, ResourceMetadataKeyEnum<List<T>> key) { 1501 List<? extends T> securityLabels = key.get(resource); 1502 if (securityLabels == null) { 1503 securityLabels = Collections.emptyList(); 1504 } 1505 return new ArrayList<>(securityLabels); 1506 } 1507 1508 static boolean hasNoExtensions(IBase theElement) { 1509 if (theElement instanceof ISupportsUndeclaredExtensions) { 1510 ISupportsUndeclaredExtensions res = (ISupportsUndeclaredExtensions) theElement; 1511 if (res.getUndeclaredExtensions().size() > 0 || res.getUndeclaredModifierExtensions().size() > 0) { 1512 return false; 1513 } 1514 } 1515 if (theElement instanceof IBaseHasExtensions) { 1516 IBaseHasExtensions res = (IBaseHasExtensions) theElement; 1517 if (res.hasExtension()) { 1518 return false; 1519 } 1520 } 1521 if (theElement instanceof IBaseHasModifierExtensions) { 1522 IBaseHasModifierExtensions res = (IBaseHasModifierExtensions) theElement; 1523 return !res.hasModifierExtension(); 1524 } 1525 return true; 1526 } 1527 1528}