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