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