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}