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