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