001package ca.uhn.fhir.parser;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2021 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.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.lang3.StringUtils;
049import org.apache.commons.lang3.Validate;
050import org.hl7.fhir.instance.model.api.IBase;
051import org.hl7.fhir.instance.model.api.IBaseBundle;
052import org.hl7.fhir.instance.model.api.IBaseCoding;
053import org.hl7.fhir.instance.model.api.IBaseElement;
054import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
055import org.hl7.fhir.instance.model.api.IBaseHasModifierExtensions;
056import org.hl7.fhir.instance.model.api.IBaseMetaType;
057import org.hl7.fhir.instance.model.api.IBaseReference;
058import org.hl7.fhir.instance.model.api.IBaseResource;
059import org.hl7.fhir.instance.model.api.IIdType;
060import org.hl7.fhir.instance.model.api.IPrimitiveType;
061
062import javax.annotation.Nullable;
063import java.io.IOException;
064import java.io.InputStream;
065import java.io.InputStreamReader;
066import java.io.Reader;
067import java.io.StringReader;
068import java.io.StringWriter;
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                                        } else if (theContainedResource) {
202                                                continue;
203                                        }
204                                } else if (myNext.getDef() instanceof RuntimeChildContainedResources) {
205                                        if (theContainedResource) {
206                                                continue;
207                                        }
208                                }
209                                result.add(myNext);
210                        }
211                        return result;
212                });
213        }
214
215
216        private String determineReferenceText(IBaseReference theRef, CompositeChildElement theCompositeChildElement) {
217                IIdType ref = theRef.getReferenceElement();
218                if (isBlank(ref.getIdPart())) {
219                        String reference = ref.getValue();
220                        if (theRef.getResource() != null) {
221                                IIdType containedId = getContainedResources().getResourceId(theRef.getResource());
222                                if (containedId != null && !containedId.isEmpty()) {
223                                        if (containedId.isLocal()) {
224                                                reference = containedId.getValue();
225                                        } else {
226                                                reference = "#" + containedId.getValue();
227                                        }
228                                } else {
229                                        IIdType refId = theRef.getResource().getIdElement();
230                                        if (refId != null) {
231                                                if (refId.hasIdPart()) {
232                                                        if (refId.getValue().startsWith("urn:")) {
233                                                                reference = refId.getValue();
234                                                        } else {
235                                                                if (!refId.hasResourceType()) {
236                                                                        refId = refId.withResourceType(myContext.getResourceDefinition(theRef.getResource()).getName());
237                                                                }
238                                                                if (isStripVersionsFromReferences(theCompositeChildElement)) {
239                                                                        reference = refId.toVersionless().getValue();
240                                                                } else {
241                                                                        reference = refId.getValue();
242                                                                }
243                                                        }
244                                                }
245                                        }
246                                }
247                        }
248                        return reference;
249                }
250                if (!ref.hasResourceType() && !ref.isLocal() && theRef.getResource() != null) {
251                        ref = ref.withResourceType(myContext.getResourceDefinition(theRef.getResource()).getName());
252                }
253                if (isNotBlank(myServerBaseUrl) && StringUtils.equals(myServerBaseUrl, ref.getBaseUrl())) {
254                        if (isStripVersionsFromReferences(theCompositeChildElement)) {
255                                return ref.toUnqualifiedVersionless().getValue();
256                        }
257                        return ref.toUnqualified().getValue();
258                }
259                if (isStripVersionsFromReferences(theCompositeChildElement)) {
260                        return ref.toVersionless().getValue();
261                }
262                return ref.getValue();
263        }
264
265        protected abstract void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter, EncodeContext theEncodeContext) throws IOException, DataFormatException;
266
267        protected abstract <T extends IBaseResource> T doParseResource(Class<T> theResourceType, Reader theReader) throws DataFormatException;
268
269        @Override
270        public String encodeResourceToString(IBaseResource theResource) throws DataFormatException {
271                Writer stringWriter = new StringWriter();
272                try {
273                        encodeResourceToWriter(theResource, stringWriter);
274                } catch (IOException e) {
275                        throw new Error("Encountered IOException during write to string - This should not happen!");
276                }
277                return stringWriter.toString();
278        }
279
280        @Override
281        public final void encodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws IOException, DataFormatException {
282                EncodeContext encodeContext = new EncodeContext();
283
284                encodeResourceToWriter(theResource, theWriter, encodeContext);
285        }
286
287        protected void encodeResourceToWriter(IBaseResource theResource, Writer theWriter, EncodeContext theEncodeContext) throws IOException {
288                Validate.notNull(theResource, "theResource can not be null");
289                Validate.notNull(theWriter, "theWriter can not be null");
290                Validate.notNull(theEncodeContext, "theEncodeContext can not be null");
291
292                if (theResource.getStructureFhirVersionEnum() != myContext.getVersion().getVersion()) {
293                        throw new IllegalArgumentException(
294                                "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.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("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(b.toString());
900                }
901                throw new DataFormatException(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        class ChildNameAndDef {
916
917                private final BaseRuntimeElementDefinition<?> myChildDef;
918                private final String myChildName;
919
920                public ChildNameAndDef(String theChildName, BaseRuntimeElementDefinition<?> theChildDef) {
921                        myChildName = theChildName;
922                        myChildDef = theChildDef;
923                }
924
925                public BaseRuntimeElementDefinition<?> getChildDef() {
926                        return myChildDef;
927                }
928
929                public String getChildName() {
930                        return myChildName;
931                }
932
933        }
934
935        /**
936         * EncodeContext is a shared state object that is passed around the
937         * encode process
938         */
939        public class EncodeContext extends EncodeContextPath {
940                private final Map<Key, List<BaseParser.CompositeChildElement>> myCompositeChildrenCache = new HashMap<>();
941
942                public Map<Key, List<BaseParser.CompositeChildElement>> getCompositeChildrenCache() {
943                        return myCompositeChildrenCache;
944                }
945
946        }
947
948
949        protected class CompositeChildElement {
950                private final BaseRuntimeChildDefinition myDef;
951                private final CompositeChildElement myParent;
952                private final RuntimeResourceDefinition myResDef;
953                private final EncodeContext myEncodeContext;
954
955                public CompositeChildElement(CompositeChildElement theParent, @Nullable BaseRuntimeChildDefinition theDef, EncodeContext theEncodeContext) {
956                        myDef = theDef;
957                        myParent = theParent;
958                        myResDef = null;
959                        myEncodeContext = theEncodeContext;
960
961                        if (ourLog.isTraceEnabled()) {
962                                if (theParent != null) {
963                                        StringBuilder path = theParent.buildPath();
964                                        if (path != null) {
965                                                path.append('.');
966                                                if (myDef != null) {
967                                                        path.append(myDef.getElementName());
968                                                }
969                                                ourLog.trace(" * Next path: {}", path.toString());
970                                        }
971                                }
972                        }
973
974                }
975
976                public CompositeChildElement(RuntimeResourceDefinition theResDef, EncodeContext theEncodeContext) {
977                        myResDef = theResDef;
978                        myDef = null;
979                        myParent = null;
980                        myEncodeContext = theEncodeContext;
981                }
982
983        @Override
984        public String toString() {
985            return myDef.getElementName();
986        }
987
988        private void addParent(CompositeChildElement theParent, StringBuilder theB) {
989                        if (theParent != null) {
990                                if (theParent.myResDef != null) {
991                                        theB.append(theParent.myResDef.getName());
992                                        return;
993                                }
994
995                                if (theParent.myParent != null) {
996                                        addParent(theParent.myParent, theB);
997                                }
998
999                                if (theParent.myDef != null) {
1000                                        if (theB.length() > 0) {
1001                                                theB.append('.');
1002                                        }
1003                                        theB.append(theParent.myDef.getElementName());
1004                                }
1005                        }
1006                }
1007
1008                public boolean anyPathMatches(Set<String> thePaths) {
1009                        StringBuilder b = new StringBuilder();
1010                        addParent(this, b);
1011
1012                        String path = b.toString();
1013                        return thePaths.contains(path);
1014                }
1015
1016                private StringBuilder buildPath() {
1017                        if (myResDef != null) {
1018                                StringBuilder b = new StringBuilder();
1019                                b.append(myResDef.getName());
1020                                return b;
1021                        } else if (myParent != null) {
1022                                StringBuilder b = myParent.buildPath();
1023                                if (b != null && myDef != null) {
1024                                        b.append('.');
1025                                        b.append(myDef.getElementName());
1026                                }
1027                                return b;
1028                        } else {
1029                                return null;
1030                        }
1031                }
1032
1033                private boolean checkIfParentShouldBeEncodedAndBuildPath() {
1034                        List<EncodeContextPath> encodeElements = myEncodeElements;
1035
1036                        String currentResourceName = myEncodeContext.getResourcePath().get(myEncodeContext.getResourcePath().size() - 1).getName();
1037                        if (myEncodeElementsAppliesToResourceTypes != null && !myEncodeElementsAppliesToResourceTypes.contains(currentResourceName)) {
1038                                encodeElements = null;
1039                        }
1040
1041                        boolean retVal = checkIfPathMatchesForEncoding(encodeElements, true);
1042
1043                        /*
1044                         * We force the meta tag to be encoded even if it's not specified as an element in the
1045                         * elements filter, specifically because we'll need it in order to automatically add
1046                         * the SUBSETTED tag
1047                         */
1048                        if (!retVal) {
1049                                if ("meta".equals(myEncodeContext.getLeafResourcePathFirstField()) && shouldAddSubsettedTag(myEncodeContext)) {
1050                                        // The next element is a child of the <meta> element
1051                                        retVal = true;
1052                                } else if ("meta".equals(myDef.getElementName()) && shouldAddSubsettedTag(myEncodeContext)) {
1053                                        // The next element is the <meta> element
1054                                        retVal = true;
1055                                }
1056                        }
1057
1058                        return retVal;
1059                }
1060
1061                private boolean checkIfParentShouldNotBeEncodedAndBuildPath() {
1062                        return checkIfPathMatchesForEncoding(myDontEncodeElements, false);
1063                }
1064
1065                private boolean checkIfPathMatchesForEncoding(List<EncodeContextPath> theElements, boolean theCheckingForEncodeElements) {
1066
1067                        boolean retVal = false;
1068                        if (myDef != null) {
1069                                myEncodeContext.pushPath(myDef.getElementName(), false);
1070                        }
1071
1072                        if (theCheckingForEncodeElements && isEncodeElementsAppliesToChildResourcesOnly() && myEncodeContext.getResourcePath().size() < 2) {
1073                                retVal = true;
1074                        } else if (theElements == null) {
1075                                retVal = true;
1076                        } else {
1077                                EncodeContextPath currentResourcePath = myEncodeContext.getCurrentResourcePath();
1078                                ourLog.trace("Current resource path: {}", currentResourcePath);
1079                                for (EncodeContextPath next : theElements) {
1080
1081                                        if (next.startsWith(currentResourcePath, true)) {
1082                                                if (theCheckingForEncodeElements || next.getPath().size() == currentResourcePath.getPath().size()) {
1083                                                        retVal = true;
1084                                                        break;
1085                                                }
1086                                        }
1087
1088                                        if (next.getPath().get(next.getPath().size() - 1).getName().equals("(mandatory)")) {
1089                                                if (myDef.getMin() > 0) {
1090                                                        retVal = true;
1091                                                        break;
1092                                                }
1093                                                if (currentResourcePath.getPath().size() > next.getPath().size()) {
1094                                                        retVal = true;
1095                                                        break;
1096                                                }
1097                                        }
1098
1099                                }
1100                        }
1101
1102                        if (myDef != null) {
1103                                myEncodeContext.popPath();
1104                        }
1105
1106                        return retVal;
1107                }
1108
1109                public BaseRuntimeChildDefinition getDef() {
1110                        return myDef;
1111                }
1112
1113                public CompositeChildElement getParent() {
1114                        return myParent;
1115                }
1116
1117                public boolean shouldBeEncoded(boolean theContainedResource) {
1118                        boolean retVal = true;
1119                        if (myEncodeElements != null) {
1120                                retVal = checkIfParentShouldBeEncodedAndBuildPath();
1121                        }
1122                        if (retVal && myDontEncodeElements != null) {
1123                                retVal = !checkIfParentShouldNotBeEncodedAndBuildPath();
1124                        }
1125                        if (theContainedResource) {
1126                                retVal = !notEncodeForContainedResource.contains(myDef.getElementName());
1127                        }
1128                        if (retVal && isSummaryMode() && (getDef() == null || !getDef().isSummary())) {
1129                                String resourceName = myEncodeContext.getLeafResourceName();
1130                                // Technically the spec says we shouldn't include extensions in CapabilityStatement
1131                                // but we will do so because there are people who depend on this behaviour, at least
1132                                // as of 2019-07. See
1133                                // https://github.com/smart-on-fhir/Swift-FHIR/issues/26
1134                                // for example.
1135                                if (("Conformance".equals(resourceName) || "CapabilityStatement".equals(resourceName)) &&
1136                                        ("extension".equals(myDef.getElementName()) || "extension".equals(myEncodeContext.getLeafElementName())
1137                                        )) {
1138                                        // skip
1139                                } else {
1140                                        retVal = false;
1141                                }
1142                        }
1143
1144                        return retVal;
1145                }
1146
1147                @Override
1148                public int hashCode() {
1149                        final int prime = 31;
1150                        int result = 1;
1151                        result = prime * result + ((myDef == null) ? 0 : myDef.hashCode());
1152                        result = prime * result + ((myParent == null) ? 0 : myParent.hashCode());
1153                        result = prime * result + ((myResDef == null) ? 0 : myResDef.hashCode());
1154                        result = prime * result + ((myEncodeContext == null) ? 0 : myEncodeContext.hashCode());
1155                        return result;
1156                }
1157
1158                @Override
1159                public boolean equals(Object obj) {
1160                        if (this == obj)
1161                                return true;
1162
1163                        if (obj instanceof CompositeChildElement) {
1164                                final CompositeChildElement that = (CompositeChildElement) obj;
1165                                return Objects.equals(this.getEnclosingInstance(), that.getEnclosingInstance()) &&
1166                                        Objects.equals(this.myDef, that.myDef) &&
1167                                        Objects.equals(this.myParent, that.myParent) &&
1168                                        Objects.equals(this.myResDef, that.myResDef) &&
1169                                        Objects.equals(this.myEncodeContext, that.myEncodeContext);
1170                        }
1171                        return false;
1172                }
1173
1174                private BaseParser getEnclosingInstance() {
1175                        return BaseParser.this;
1176                }
1177        }
1178
1179        private static class Key {
1180                private final BaseRuntimeElementCompositeDefinition<?> resDef;
1181                private final boolean theContainedResource;
1182                private final BaseParser.CompositeChildElement theParent;
1183                private final BaseParser.EncodeContext theEncodeContext;
1184
1185                public Key(BaseRuntimeElementCompositeDefinition<?> resDef, final boolean theContainedResource, final BaseParser.CompositeChildElement theParent, BaseParser.EncodeContext theEncodeContext) {
1186                        this.resDef = resDef;
1187                        this.theContainedResource = theContainedResource;
1188                        this.theParent = theParent;
1189                        this.theEncodeContext = theEncodeContext;
1190                }
1191
1192                @Override
1193                public int hashCode() {
1194                        final int prime = 31;
1195                        int result = 1;
1196                        result = prime * result + ((resDef == null) ? 0 : resDef.hashCode());
1197                        result = prime * result + (theContainedResource ? 1231 : 1237);
1198                        result = prime * result + ((theParent == null) ? 0 : theParent.hashCode());
1199                        result = prime * result + ((theEncodeContext == null) ? 0 : theEncodeContext.hashCode());
1200                        return result;
1201                }
1202
1203                @Override
1204                public boolean equals(final Object obj) {
1205                        if (this == obj) {
1206                                return true;
1207                        }
1208                        if (obj instanceof Key) {
1209                                final Key that = (Key) obj;
1210                                return Objects.equals(this.resDef, that.resDef) &&
1211                                        this.theContainedResource == that.theContainedResource &&
1212                                        Objects.equals(this.theParent, that.theParent) &&
1213                                        Objects.equals(this.theEncodeContext, that.theEncodeContext);
1214                        }
1215                        return false;
1216                }
1217        }
1218
1219
1220        protected static <T> List<T> extractMetadataListNotNull(IResource resource, ResourceMetadataKeyEnum<List<T>> key) {
1221                List<? extends T> securityLabels = key.get(resource);
1222                if (securityLabels == null) {
1223                        securityLabels = Collections.emptyList();
1224                }
1225                return new ArrayList<>(securityLabels);
1226        }
1227
1228        static boolean hasNoExtensions(IBase theElement) {
1229                if (theElement instanceof ISupportsUndeclaredExtensions) {
1230                        ISupportsUndeclaredExtensions res = (ISupportsUndeclaredExtensions) theElement;
1231                        if (res.getUndeclaredExtensions().size() > 0 || res.getUndeclaredModifierExtensions().size() > 0) {
1232                                return false;
1233                        }
1234                }
1235                if (theElement instanceof IBaseHasExtensions) {
1236                        IBaseHasExtensions res = (IBaseHasExtensions) theElement;
1237                        if (res.hasExtension()) {
1238                                return false;
1239                        }
1240                }
1241                if (theElement instanceof IBaseHasModifierExtensions) {
1242                        IBaseHasModifierExtensions res = (IBaseHasModifierExtensions) theElement;
1243                        return !res.hasModifierExtension();
1244                }
1245                return true;
1246        }
1247
1248}