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