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