001package ca.uhn.fhir.parser;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2017 University Health Network
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 * 
013 * http://www.apache.org/licenses/LICENSE-2.0
014 * 
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import ca.uhn.fhir.context.*;
024import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum;
025import ca.uhn.fhir.model.api.*;
026import ca.uhn.fhir.model.primitive.IdDt;
027import ca.uhn.fhir.rest.api.Constants;
028import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
029import ca.uhn.fhir.util.UrlUtil;
030import org.apache.commons.lang3.StringUtils;
031import org.apache.commons.lang3.Validate;
032import org.hl7.fhir.instance.model.api.*;
033
034import java.io.*;
035import java.lang.reflect.Modifier;
036import java.util.*;
037
038import static org.apache.commons.lang3.StringUtils.isBlank;
039import static org.apache.commons.lang3.StringUtils.isNotBlank;
040
041public abstract class BaseParser implements IParser {
042
043        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseParser.class);
044
045        private ContainedResources myContainedResources;
046
047        private FhirContext myContext;
048        private Set<String> myDontEncodeElements;
049        private boolean myDontEncodeElementsIncludesStars;
050        private Set<String> myEncodeElements;
051        private Set<String> myEncodeElementsAppliesToResourceTypes;
052        private boolean myEncodeElementsIncludesStars;
053        private IIdType myEncodeForceResourceId;
054        private IParserErrorHandler myErrorHandler;
055        private boolean myOmitResourceId;
056        private List<Class<? extends IBaseResource>> myPreferTypes;
057        private String myServerBaseUrl;
058        private Boolean myStripVersionsFromReferences;
059        private Boolean myOverrideResourceIdWithBundleEntryFullUrl;
060        private boolean mySummaryMode;
061        private boolean mySuppressNarratives;
062        private Set<String> myDontStripVersionsFromReferencesAtPaths;
063
064        /**
065         * Constructor
066         *
067         * @param theParserErrorHandler
068         */
069        public BaseParser(FhirContext theContext, IParserErrorHandler theParserErrorHandler) {
070                myContext = theContext;
071                myErrorHandler = theParserErrorHandler;
072        }
073
074        protected Iterable<CompositeChildElement> compositeChildIterator(IBase theCompositeElement, final boolean theContainedResource, final boolean theSubResource, final CompositeChildElement theParent) {
075
076                BaseRuntimeElementCompositeDefinition<?> elementDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theCompositeElement.getClass());
077                final List<BaseRuntimeChildDefinition> children = elementDef.getChildrenAndExtension();
078
079                return new Iterable<BaseParser.CompositeChildElement>() {
080                        @Override
081                        public Iterator<CompositeChildElement> iterator() {
082
083                                return new Iterator<CompositeChildElement>() {
084                                        private Iterator<? extends BaseRuntimeChildDefinition> myChildrenIter;
085                                        private Boolean myHasNext = null;
086                                        private CompositeChildElement myNext;
087
088                                        /**
089                                         * Constructor
090                                         */ {
091                                                myChildrenIter = children.iterator();
092                                        }
093
094                                        @Override
095                                        public boolean hasNext() {
096                                                if (myHasNext != null) {
097                                                        return myHasNext;
098                                                }
099
100                                                myNext = null;
101                                                do {
102                                                        if (myChildrenIter.hasNext() == false) {
103                                                                myHasNext = Boolean.FALSE;
104                                                                return false;
105                                                        }
106
107                                                        myNext = new CompositeChildElement(theParent, myChildrenIter.next(), theSubResource);
108
109                                                        /*
110                                                         * There are lots of reasons we might skip encoding a particular child
111                                                         */
112                                                        if (myNext.getDef().getElementName().equals("id")) {
113                                                                myNext = null;
114                                                        } else if (!myNext.shouldBeEncoded()) {
115                                                                myNext = null;
116                                                        } else if (isSummaryMode() && !myNext.getDef().isSummary()) {
117                                                                myNext = null;
118                                                        } else if (myNext.getDef() instanceof RuntimeChildNarrativeDefinition) {
119                                                                if (isSuppressNarratives() || isSummaryMode()) {
120                                                                        myNext = null;
121                                                                } else if (theContainedResource) {
122                                                                        myNext = null;
123                                                                }
124                                                        } else if (myNext.getDef() instanceof RuntimeChildContainedResources) {
125                                                                if (theContainedResource) {
126                                                                        myNext = null;
127                                                                }
128                                                        }
129
130                                                } while (myNext == null);
131
132                                                myHasNext = true;
133                                                return true;
134                                        }
135
136                                        @Override
137                                        public CompositeChildElement next() {
138                                                if (myHasNext == null) {
139                                                        if (!hasNext()) {
140                                                                throw new IllegalStateException();
141                                                        }
142                                                }
143                                                CompositeChildElement retVal = myNext;
144                                                myNext = null;
145                                                myHasNext = null;
146                                                return retVal;
147                                        }
148
149                                        @Override
150                                        public void remove() {
151                                                throw new UnsupportedOperationException();
152                                        }
153                                };
154                        }
155                };
156        }
157
158        private void containResourcesForEncoding(ContainedResources theContained, IBaseResource theResource, IBaseResource theTarget) {
159                Set<String> allIds = new HashSet<String>();
160                Map<String, IBaseResource> existingIdToContainedResource = null;
161
162                if (theTarget instanceof IResource) {
163                        List<? extends IResource> containedResources = ((IResource) theTarget).getContained().getContainedResources();
164                        for (IResource next : containedResources) {
165                                String nextId = next.getId().getValue();
166                                if (StringUtils.isNotBlank(nextId)) {
167                                        if (!nextId.startsWith("#")) {
168                                                nextId = '#' + nextId;
169                                        }
170                                        allIds.add(nextId);
171                                        if (existingIdToContainedResource == null) {
172                                                existingIdToContainedResource = new HashMap<String, IBaseResource>();
173                                        }
174                                        existingIdToContainedResource.put(nextId, next);
175                                }
176                        }
177                } else if (theTarget instanceof IDomainResource) {
178                        List<? extends IAnyResource> containedResources = ((IDomainResource) theTarget).getContained();
179                        for (IAnyResource next : containedResources) {
180                                String nextId = next.getIdElement().getValue();
181                                if (StringUtils.isNotBlank(nextId)) {
182                                        if (!nextId.startsWith("#")) {
183                                                nextId = '#' + nextId;
184                                        }
185                                        allIds.add(nextId);
186                                        if (existingIdToContainedResource == null) {
187                                                existingIdToContainedResource = new HashMap<String, IBaseResource>();
188                                        }
189                                        existingIdToContainedResource.put(nextId, next);
190                                }
191                        }
192                } else {
193                        // no resources to contain
194                }
195
196                {
197                        List<IBaseReference> allElements = myContext.newTerser().getAllPopulatedChildElementsOfType(theResource, IBaseReference.class);
198                        for (IBaseReference next : allElements) {
199                                IBaseResource resource = next.getResource();
200                                if (resource != null) {
201                                        if (resource.getIdElement().isEmpty() || resource.getIdElement().isLocal()) {
202                                                if (theContained.getResourceId(resource) != null) {
203                                                        // Prevent infinite recursion if there are circular loops in the contained resources
204                                                        continue;
205                                                }
206                                                theContained.addContained(resource);
207                                                if (resource.getIdElement().isLocal() && existingIdToContainedResource != null) {
208                                                        existingIdToContainedResource.remove(resource.getIdElement().getValue());
209                                                }
210                                        } else {
211                                                continue;
212                                        }
213
214                                        containResourcesForEncoding(theContained, resource, theTarget);
215                                } else if (next.getReferenceElement().isLocal()) {
216                                        if (existingIdToContainedResource != null) {
217                                                IBaseResource potentialTarget = existingIdToContainedResource.remove(next.getReferenceElement().getValue());
218                                                if (potentialTarget != null) {
219                                                        theContained.addContained(next.getReferenceElement(), potentialTarget);
220                                                        containResourcesForEncoding(theContained, potentialTarget, theTarget);
221                                                }
222                                        }
223                                }
224                        }
225                }
226
227        }
228
229        protected void containResourcesForEncoding(IBaseResource theResource) {
230                ContainedResources contained = new ContainedResources();
231                containResourcesForEncoding(contained, theResource, theResource);
232                myContainedResources = contained;
233        }
234
235        private String determineReferenceText(IBaseReference theRef, CompositeChildElement theCompositeChildElement) {
236                IIdType ref = theRef.getReferenceElement();
237                if (isBlank(ref.getIdPart())) {
238                        String reference = ref.getValue();
239                        if (theRef.getResource() != null) {
240                                IIdType containedId = getContainedResources().getResourceId(theRef.getResource());
241                                if (containedId != null && !containedId.isEmpty()) {
242                                        if (containedId.isLocal()) {
243                                                reference = containedId.getValue();
244                                        } else {
245                                                reference = "#" + containedId.getValue();
246                                        }
247                                } else {
248                                        IIdType refId = theRef.getResource().getIdElement();
249                                        if (refId != null) {
250                                                if (refId.hasIdPart()) {
251                                                        if (refId.getValue().startsWith("urn:")) {
252                                                                reference = refId.getValue();
253                                                        } else {
254                                                                if (!refId.hasResourceType()) {
255                                                                        refId = refId.withResourceType(myContext.getResourceDefinition(theRef.getResource()).getName());
256                                                                }
257                                                                if (isStripVersionsFromReferences(theCompositeChildElement)) {
258                                                                        reference = refId.toVersionless().getValue();
259                                                                } else {
260                                                                        reference = refId.getValue();
261                                                                }
262                                                        }
263                                                }
264                                        }
265                                }
266                        }
267                        return reference;
268                }
269                if (!ref.hasResourceType() && !ref.isLocal() && theRef.getResource() != null) {
270                        ref = ref.withResourceType(myContext.getResourceDefinition(theRef.getResource()).getName());
271                }
272                if (isNotBlank(myServerBaseUrl) && StringUtils.equals(myServerBaseUrl, ref.getBaseUrl())) {
273                        if (isStripVersionsFromReferences(theCompositeChildElement)) {
274                                return ref.toUnqualifiedVersionless().getValue();
275                        }
276                        return ref.toUnqualified().getValue();
277                }
278                if (isStripVersionsFromReferences(theCompositeChildElement)) {
279                        return ref.toVersionless().getValue();
280                }
281                return ref.getValue();
282        }
283
284        protected abstract void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws IOException, DataFormatException;
285
286        protected abstract <T extends IBaseResource> T doParseResource(Class<T> theResourceType, Reader theReader) throws DataFormatException;
287
288        @Override
289        public String encodeResourceToString(IBaseResource theResource) throws DataFormatException {
290                Writer stringWriter = new StringWriter();
291                try {
292                        encodeResourceToWriter(theResource, stringWriter);
293                } catch (IOException e) {
294                        throw new Error("Encountered IOException during write to string - This should not happen!");
295                }
296                return stringWriter.toString();
297        }
298
299        @Override
300        public final void encodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws IOException, DataFormatException {
301                Validate.notNull(theResource, "theResource can not be null");
302                Validate.notNull(theWriter, "theWriter can not be null");
303
304                if (theResource.getStructureFhirVersionEnum() != myContext.getVersion().getVersion()) {
305                        throw new IllegalArgumentException(
306                                "This parser is for FHIR version " + myContext.getVersion().getVersion() + " - Can not encode a structure for version " + theResource.getStructureFhirVersionEnum());
307                }
308
309                doEncodeResourceToWriter(theResource, theWriter);
310        }
311
312        private void filterCodingsWithNoCodeOrSystem(List<? extends IBaseCoding> tagList) {
313                for (int i = 0; i < tagList.size(); i++) {
314                        if (isBlank(tagList.get(i).getCode()) && isBlank(tagList.get(i).getSystem())) {
315                                tagList.remove(i);
316                                i--;
317                        }
318                }
319        }
320
321        protected IIdType fixContainedResourceId(String theValue) {
322                IIdType retVal = (IIdType) myContext.getElementDefinition("id").newInstance();
323                if (StringUtils.isNotBlank(theValue) && theValue.charAt(0) == '#') {
324                        retVal.setValue(theValue.substring(1));
325                } else {
326                        retVal.setValue(theValue);
327                }
328                return retVal;
329        }
330
331        @SuppressWarnings("unchecked")
332        ChildNameAndDef getChildNameAndDef(BaseRuntimeChildDefinition theChild, IBase theValue) {
333                Class<? extends IBase> type = theValue.getClass();
334                String childName = theChild.getChildNameByDatatype(type);
335                BaseRuntimeElementDefinition<?> childDef = theChild.getChildElementDefinitionByDatatype(type);
336                if (childDef == null) {
337                        // if (theValue instanceof IBaseExtension) {
338                        // return null;
339                        // }
340
341                        /*
342                         * For RI structures Enumeration class, this replaces the child def
343                         * with the "code" one. This is messy, and presumably there is a better
344                         * way..
345                         */
346                        BaseRuntimeElementDefinition<?> elementDef = myContext.getElementDefinition(type);
347                        if (elementDef.getName().equals("code")) {
348                                Class<? extends IBase> type2 = myContext.getElementDefinition("code").getImplementingClass();
349                                childDef = theChild.getChildElementDefinitionByDatatype(type2);
350                                childName = theChild.getChildNameByDatatype(type2);
351                        }
352
353                        // See possibly the user has extended a built-in type without
354                        // declaring it anywhere, as in XmlParserDstu3Test#testEncodeUndeclaredBlock
355                        if (childDef == null) {
356                                Class<?> nextSuperType = theValue.getClass();
357                                while (IBase.class.isAssignableFrom(nextSuperType) && childDef == null) {
358                                        if (Modifier.isAbstract(nextSuperType.getModifiers()) == false) {
359                                                BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition((Class<? extends IBase>) nextSuperType);
360                                                Class<?> nextChildType = def.getImplementingClass();
361                                                childDef = theChild.getChildElementDefinitionByDatatype((Class<? extends IBase>) nextChildType);
362                                                childName = theChild.getChildNameByDatatype((Class<? extends IBase>) nextChildType);
363                                        }
364                                        nextSuperType = nextSuperType.getSuperclass();
365                                }
366                        }
367
368                        if (childDef == null) {
369                                throwExceptionForUnknownChildType(theChild, type);
370                        }
371                }
372
373                return new ChildNameAndDef(childName, childDef);
374        }
375
376        protected String getCompositeElementId(IBase theElement) {
377                String elementId = null;
378                if (!(theElement instanceof IBaseResource)) {
379                        if (theElement instanceof IBaseElement) {
380                                elementId = ((IBaseElement) theElement).getId();
381                        } else if (theElement instanceof IIdentifiableElement) {
382                                elementId = ((IIdentifiableElement) theElement).getElementSpecificId();
383                        }
384                }
385                return elementId;
386        }
387
388        ContainedResources getContainedResources() {
389                return myContainedResources;
390        }
391
392        @Override
393        public Set<String> getDontStripVersionsFromReferencesAtPaths() {
394                return myDontStripVersionsFromReferencesAtPaths;
395        }
396
397        /**
398         * See {@link #setEncodeElements(Set)}
399         */
400        @Override
401        public Set<String> getEncodeElements() {
402                return myEncodeElements;
403        }
404
405        @Override
406        public void setEncodeElements(Set<String> theEncodeElements) {
407                myEncodeElementsIncludesStars = false;
408                if (theEncodeElements == null || theEncodeElements.isEmpty()) {
409                        myEncodeElements = null;
410                } else {
411                        myEncodeElements = theEncodeElements;
412                        for (String next : theEncodeElements) {
413                                if (next.startsWith("*.")) {
414                                        myEncodeElementsIncludesStars = true;
415                                }
416                        }
417                }
418        }
419
420        /**
421         * See {@link #setEncodeElementsAppliesToResourceTypes(Set)}
422         */
423        @Override
424        public Set<String> getEncodeElementsAppliesToResourceTypes() {
425                return myEncodeElementsAppliesToResourceTypes;
426        }
427
428        @Override
429        public void setEncodeElementsAppliesToResourceTypes(Set<String> theEncodeElementsAppliesToResourceTypes) {
430                if (theEncodeElementsAppliesToResourceTypes == null || theEncodeElementsAppliesToResourceTypes.isEmpty()) {
431                        myEncodeElementsAppliesToResourceTypes = null;
432                } else {
433                        myEncodeElementsAppliesToResourceTypes = theEncodeElementsAppliesToResourceTypes;
434                }
435        }
436
437        @Override
438        public IIdType getEncodeForceResourceId() {
439                return myEncodeForceResourceId;
440        }
441
442        @Override
443        public BaseParser setEncodeForceResourceId(IIdType theEncodeForceResourceId) {
444                myEncodeForceResourceId = theEncodeForceResourceId;
445                return this;
446        }
447
448        protected IParserErrorHandler getErrorHandler() {
449                return myErrorHandler;
450        }
451
452        protected String getExtensionUrl(final String extensionUrl) {
453                String url = extensionUrl;
454                if (StringUtils.isNotBlank(extensionUrl) && StringUtils.isNotBlank(myServerBaseUrl)) {
455                        url = !UrlUtil.isValid(extensionUrl) && extensionUrl.startsWith("/") ? myServerBaseUrl + extensionUrl : extensionUrl;
456                }
457                return url;
458        }
459
460        protected TagList getMetaTagsForEncoding(IResource theIResource) {
461                TagList tags = ResourceMetadataKeyEnum.TAG_LIST.get(theIResource);
462                if (shouldAddSubsettedTag()) {
463                        tags = new TagList(tags);
464                        tags.add(new Tag(Constants.TAG_SUBSETTED_SYSTEM, Constants.TAG_SUBSETTED_CODE, subsetDescription()));
465                }
466
467                return tags;
468        }
469
470        @Override
471        public Boolean getOverrideResourceIdWithBundleEntryFullUrl() {
472                return myOverrideResourceIdWithBundleEntryFullUrl;
473        }
474
475        @Override
476        public List<Class<? extends IBaseResource>> getPreferTypes() {
477                return myPreferTypes;
478        }
479
480        @Override
481        public void setPreferTypes(List<Class<? extends IBaseResource>> thePreferTypes) {
482                if (thePreferTypes != null) {
483                        ArrayList<Class<? extends IBaseResource>> types = new ArrayList<Class<? extends IBaseResource>>();
484                        for (Class<? extends IBaseResource> next : thePreferTypes) {
485                                if (Modifier.isAbstract(next.getModifiers()) == false) {
486                                        types.add(next);
487                                }
488                        }
489                        myPreferTypes = Collections.unmodifiableList(types);
490                } else {
491                        myPreferTypes = thePreferTypes;
492                }
493        }
494
495        @SuppressWarnings("deprecation")
496        protected <T extends IPrimitiveType<String>> List<T> getProfileTagsForEncoding(IBaseResource theResource, List<T> theProfiles) {
497                switch (myContext.getAddProfileTagWhenEncoding()) {
498                        case NEVER:
499                                return theProfiles;
500                        case ONLY_FOR_CUSTOM:
501                                RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource);
502                                if (resDef.isStandardType()) {
503                                        return theProfiles;
504                                }
505                                break;
506                        case ALWAYS:
507                                break;
508                }
509
510                RuntimeResourceDefinition nextDef = myContext.getResourceDefinition(theResource);
511                String profile = nextDef.getResourceProfile(myServerBaseUrl);
512                if (isNotBlank(profile)) {
513                        for (T next : theProfiles) {
514                                if (profile.equals(next.getValue())) {
515                                        return theProfiles;
516                                }
517                        }
518
519                        List<T> newList = new ArrayList<T>();
520                        newList.addAll(theProfiles);
521
522                        BaseRuntimeElementDefinition<?> idElement = myContext.getElementDefinition("id");
523                        @SuppressWarnings("unchecked")
524                        T newId = (T) idElement.newInstance();
525                        newId.setValue(profile);
526
527                        newList.add(newId);
528                        return newList;
529                }
530
531                return theProfiles;
532        }
533
534        protected String getServerBaseUrl() {
535                return myServerBaseUrl;
536        }
537
538        @Override
539        public Boolean getStripVersionsFromReferences() {
540                return myStripVersionsFromReferences;
541        }
542
543        /**
544         * If set to <code>true</code> (default is <code>false</code>), narratives will not be included in the encoded
545         * values.
546         *
547         * @deprecated Use {@link #isSuppressNarratives()}
548         */
549        @Deprecated
550        public boolean getSuppressNarratives() {
551                return mySuppressNarratives;
552        }
553
554        protected boolean isChildContained(BaseRuntimeElementDefinition<?> childDef, boolean theIncludedResource) {
555                return (childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCES || childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST) && getContainedResources().isEmpty() == false
556                        && theIncludedResource == false;
557        }
558
559        @Override
560        public boolean isOmitResourceId() {
561                return myOmitResourceId;
562        }
563
564        private boolean isOverrideResourceIdWithBundleEntryFullUrl() {
565                Boolean overrideResourceIdWithBundleEntryFullUrl = myOverrideResourceIdWithBundleEntryFullUrl;
566                if (overrideResourceIdWithBundleEntryFullUrl != null) {
567                        return overrideResourceIdWithBundleEntryFullUrl;
568                }
569
570                return myContext.getParserOptions().isOverrideResourceIdWithBundleEntryFullUrl();
571        }
572
573        private boolean isStripVersionsFromReferences(CompositeChildElement theCompositeChildElement) {
574                Boolean stripVersionsFromReferences = myStripVersionsFromReferences;
575                if (stripVersionsFromReferences != null) {
576                        return stripVersionsFromReferences;
577                }
578
579                if (myContext.getParserOptions().isStripVersionsFromReferences() == false) {
580                        return false;
581                }
582
583                Set<String> dontStripVersionsFromReferencesAtPaths = myDontStripVersionsFromReferencesAtPaths;
584                if (dontStripVersionsFromReferencesAtPaths != null) {
585                        if (dontStripVersionsFromReferencesAtPaths.isEmpty() == false && theCompositeChildElement.anyPathMatches(dontStripVersionsFromReferencesAtPaths)) {
586                                return false;
587                        }
588                }
589
590                dontStripVersionsFromReferencesAtPaths = myContext.getParserOptions().getDontStripVersionsFromReferencesAtPaths();
591                if (dontStripVersionsFromReferencesAtPaths.isEmpty() == false && theCompositeChildElement.anyPathMatches(dontStripVersionsFromReferencesAtPaths)) {
592                        return false;
593                }
594
595                return true;
596        }
597
598        @Override
599        public boolean isSummaryMode() {
600                return mySummaryMode;
601        }
602
603        /**
604         * If set to <code>true</code> (default is <code>false</code>), narratives will not be included in the encoded
605         * values.
606         *
607         * @since 1.2
608         */
609        public boolean isSuppressNarratives() {
610                return mySuppressNarratives;
611        }
612
613        @Override
614        public <T extends IBaseResource> T parseResource(Class<T> theResourceType, Reader theReader) throws DataFormatException {
615
616                /*
617                 * We do this so that the context can verify that the structure is for
618                 * the correct FHIR version
619                 */
620                if (theResourceType != null) {
621                        myContext.getResourceDefinition(theResourceType);
622                }
623
624                // Actually do the parse
625                T retVal = doParseResource(theResourceType, theReader);
626
627                RuntimeResourceDefinition def = myContext.getResourceDefinition(retVal);
628                if ("Bundle".equals(def.getName())) {
629
630                        BaseRuntimeChildDefinition entryChild = def.getChildByName("entry");
631                        BaseRuntimeElementCompositeDefinition<?> entryDef = (BaseRuntimeElementCompositeDefinition<?>) entryChild.getChildByName("entry");
632                        List<IBase> entries = entryChild.getAccessor().getValues(retVal);
633                        if (entries != null) {
634                                for (IBase nextEntry : entries) {
635
636                                        /**
637                                         * If Bundle.entry.fullUrl is populated, set the resource ID to that
638                                         */
639                                        // TODO: should emit a warning and maybe notify the error handler if the resource ID doesn't match the
640                                        // fullUrl idPart
641                                        BaseRuntimeChildDefinition fullUrlChild = entryDef.getChildByName("fullUrl");
642                                        if (fullUrlChild == null) {
643                                                continue; // TODO: remove this once the data model in tinder plugin catches up to 1.2
644                                        }
645                                        if (isOverrideResourceIdWithBundleEntryFullUrl()) {
646                                                List<IBase> fullUrl = fullUrlChild.getAccessor().getValues(nextEntry);
647                                                if (fullUrl != null && !fullUrl.isEmpty()) {
648                                                        IPrimitiveType<?> value = (IPrimitiveType<?>) fullUrl.get(0);
649                                                        if (value.isEmpty() == false) {
650                                                                List<IBase> entryResources = entryDef.getChildByName("resource").getAccessor().getValues(nextEntry);
651                                                                if (entryResources != null && entryResources.size() > 0) {
652                                                                        IBaseResource res = (IBaseResource) entryResources.get(0);
653                                                                        String versionId = res.getIdElement().getVersionIdPart();
654                                                                        res.setId(value.getValueAsString());
655                                                                        if (isNotBlank(versionId) && res.getIdElement().hasVersionIdPart() == false) {
656                                                                                res.setId(res.getIdElement().withVersion(versionId));
657                                                                        }
658                                                                }
659                                                        }
660                                                }
661                                        }
662                                }
663                        }
664
665                }
666
667                return retVal;
668        }
669
670        @SuppressWarnings("cast")
671        @Override
672        public <T extends IBaseResource> T parseResource(Class<T> theResourceType, String theMessageString) {
673                StringReader reader = new StringReader(theMessageString);
674                return (T) parseResource(theResourceType, reader);
675        }
676
677        @Override
678        public IBaseResource parseResource(Reader theReader) throws ConfigurationException, DataFormatException {
679                return parseResource(null, theReader);
680        }
681
682        @Override
683        public IBaseResource parseResource(String theMessageString) throws ConfigurationException, DataFormatException {
684                return parseResource(null, theMessageString);
685        }
686
687        protected List<? extends IBase> preProcessValues(BaseRuntimeChildDefinition theMetaChildUncast, IBaseResource theResource, List<? extends IBase> theValues,
688                                                                                                                                         CompositeChildElement theCompositeChildElement) {
689                if (myContext.getVersion().getVersion().isRi()) {
690
691                        /*
692                         * If we're encoding the meta tag, we do some massaging of the meta values before
693                         * encoding. But if there is no meta element at all, we create one since we're possibly going to be
694                         * adding things to it
695                         */
696                        if (theValues.isEmpty() && theMetaChildUncast.getElementName().equals("meta")) {
697                                BaseRuntimeElementDefinition<?> metaChild = theMetaChildUncast.getChildByName("meta");
698                                if (IBaseMetaType.class.isAssignableFrom(metaChild.getImplementingClass())) {
699                                        IBaseMetaType newType = (IBaseMetaType) metaChild.newInstance();
700                                        theValues = Collections.singletonList(newType);
701                                }
702                        }
703
704                        if (theValues.size() == 1 && theValues.get(0) instanceof IBaseMetaType) {
705
706                                IBaseMetaType metaValue = (IBaseMetaType) theValues.get(0);
707                                try {
708                                        metaValue = (IBaseMetaType) metaValue.getClass().getMethod("copy").invoke(metaValue);
709                                } catch (Exception e) {
710                                        throw new InternalErrorException("Failed to duplicate meta", e);
711                                }
712
713                                if (isBlank(metaValue.getVersionId())) {
714                                        if (theResource.getIdElement().hasVersionIdPart()) {
715                                                metaValue.setVersionId(theResource.getIdElement().getVersionIdPart());
716                                        }
717                                }
718
719                                filterCodingsWithNoCodeOrSystem(metaValue.getTag());
720                                filterCodingsWithNoCodeOrSystem(metaValue.getSecurity());
721
722                                List<? extends IPrimitiveType<String>> newProfileList = getProfileTagsForEncoding(theResource, metaValue.getProfile());
723                                List<? extends IPrimitiveType<String>> oldProfileList = metaValue.getProfile();
724                                if (oldProfileList != newProfileList) {
725                                        oldProfileList.clear();
726                                        for (IPrimitiveType<String> next : newProfileList) {
727                                                if (isNotBlank(next.getValue())) {
728                                                        metaValue.addProfile(next.getValue());
729                                                }
730                                        }
731                                }
732
733                                if (shouldAddSubsettedTag()) {
734                                        IBaseCoding coding = metaValue.addTag();
735                                        coding.setCode(Constants.TAG_SUBSETTED_CODE);
736                                        coding.setSystem(Constants.TAG_SUBSETTED_SYSTEM);
737                                        coding.setDisplay(subsetDescription());
738                                }
739
740                                return Collections.singletonList(metaValue);
741                        }
742                }
743
744                @SuppressWarnings("unchecked")
745                List<IBase> retVal = (List<IBase>) theValues;
746
747                for (int i = 0; i < retVal.size(); i++) {
748                        IBase next = retVal.get(i);
749
750                        /*
751                         * If we have automatically contained any resources via
752                         * their references, this ensures that we output the new
753                         * local reference
754                         */
755                        if (next instanceof IBaseReference) {
756                                IBaseReference nextRef = (IBaseReference) next;
757                                String refText = determineReferenceText(nextRef, theCompositeChildElement);
758                                if (!StringUtils.equals(refText, nextRef.getReferenceElement().getValue())) {
759
760                                        if (retVal == theValues) {
761                                                retVal = new ArrayList<IBase>(theValues);
762                                        }
763                                        IBaseReference newRef = (IBaseReference) myContext.getElementDefinition(nextRef.getClass()).newInstance();
764                                        myContext.newTerser().cloneInto(nextRef, newRef, true);
765                                        newRef.setReference(refText);
766                                        retVal.set(i, newRef);
767
768                                }
769                        }
770                }
771
772                return retVal;
773        }
774
775        @Override
776        public void setDontEncodeElements(Set<String> theDontEncodeElements) {
777                myDontEncodeElementsIncludesStars = false;
778                if (theDontEncodeElements == null || theDontEncodeElements.isEmpty()) {
779                        myDontEncodeElements = null;
780                } else {
781                        myDontEncodeElements = theDontEncodeElements;
782                        for (String next : theDontEncodeElements) {
783                                if (next.startsWith("*.")) {
784                                        myDontEncodeElementsIncludesStars = true;
785                                }
786                        }
787                }
788        }
789
790        @Override
791        public IParser setDontStripVersionsFromReferencesAtPaths(String... thePaths) {
792                if (thePaths == null) {
793                        setDontStripVersionsFromReferencesAtPaths((List<String>) null);
794                } else {
795                        setDontStripVersionsFromReferencesAtPaths(Arrays.asList(thePaths));
796                }
797                return this;
798        }
799
800        @SuppressWarnings("unchecked")
801        @Override
802        public IParser setDontStripVersionsFromReferencesAtPaths(Collection<String> thePaths) {
803                if (thePaths == null) {
804                        myDontStripVersionsFromReferencesAtPaths = Collections.emptySet();
805                } else if (thePaths instanceof HashSet) {
806                        myDontStripVersionsFromReferencesAtPaths = (Set<String>) ((HashSet<String>) thePaths).clone();
807                } else {
808                        myDontStripVersionsFromReferencesAtPaths = new HashSet<String>(thePaths);
809                }
810                return this;
811        }
812
813        @Override
814        public IParser setOmitResourceId(boolean theOmitResourceId) {
815                myOmitResourceId = theOmitResourceId;
816                return this;
817        }
818
819        @Override
820        public IParser setOverrideResourceIdWithBundleEntryFullUrl(Boolean theOverrideResourceIdWithBundleEntryFullUrl) {
821                myOverrideResourceIdWithBundleEntryFullUrl = theOverrideResourceIdWithBundleEntryFullUrl;
822                return this;
823        }
824
825        @Override
826        public IParser setParserErrorHandler(IParserErrorHandler theErrorHandler) {
827                Validate.notNull(theErrorHandler, "theErrorHandler must not be null");
828                myErrorHandler = theErrorHandler;
829                return this;
830        }
831
832        @Override
833        public IParser setServerBaseUrl(String theUrl) {
834                myServerBaseUrl = isNotBlank(theUrl) ? theUrl : null;
835                return this;
836        }
837
838        @Override
839        public IParser setStripVersionsFromReferences(Boolean theStripVersionsFromReferences) {
840                myStripVersionsFromReferences = theStripVersionsFromReferences;
841                return this;
842        }
843
844        @Override
845        public IParser setSummaryMode(boolean theSummaryMode) {
846                mySummaryMode = theSummaryMode;
847                return this;
848        }
849
850        @Override
851        public IParser setSuppressNarratives(boolean theSuppressNarratives) {
852                mySuppressNarratives = theSuppressNarratives;
853                return this;
854        }
855
856        protected boolean shouldAddSubsettedTag() {
857                return isSummaryMode() || isSuppressNarratives() || getEncodeElements() != null;
858        }
859
860        protected boolean shouldEncodeResourceId(IBaseResource theResource, boolean theSubResource) {
861                boolean retVal = true;
862                if (isOmitResourceId()) {
863                        retVal = false;
864                } else {
865                        if (myDontEncodeElements != null) {
866                                String resourceName = myContext.getResourceDefinition(theResource).getName();
867                                if (myDontEncodeElements.contains(resourceName + ".id")) {
868                                        retVal = false;
869                                } else if (myDontEncodeElements.contains("*.id")) {
870                                        retVal = false;
871                                } else if (theSubResource == false && myDontEncodeElements.contains("id")) {
872                                        retVal = false;
873                                }
874                        }
875                }
876                return retVal;
877        }
878
879        /**
880         * Used for DSTU2 only
881         */
882        protected boolean shouldEncodeResourceMeta(IResource theResource) {
883                if (myDontEncodeElements != null) {
884                        String resourceName = myContext.getResourceDefinition(theResource).getName();
885                        if (myDontEncodeElements.contains(resourceName + ".meta")) {
886                                return false;
887                        } else if (myDontEncodeElements.contains("*.meta")) {
888                                return false;
889                        }
890                }
891                return true;
892        }
893
894        private String subsetDescription() {
895                return "Resource encoded in summary mode";
896        }
897
898        protected void throwExceptionForUnknownChildType(BaseRuntimeChildDefinition nextChild, Class<? extends IBase> theType) {
899                if (nextChild instanceof BaseRuntimeDeclaredChildDefinition) {
900                        StringBuilder b = new StringBuilder();
901                        b.append(((BaseRuntimeDeclaredChildDefinition) nextChild).getElementName());
902                        b.append(" has type ");
903                        b.append(theType.getName());
904                        b.append(" but this is not a valid type for this element");
905                        if (nextChild instanceof RuntimeChildChoiceDefinition) {
906                                RuntimeChildChoiceDefinition choice = (RuntimeChildChoiceDefinition) nextChild;
907                                b.append(" - Expected one of: " + choice.getValidChildTypes());
908                        }
909                        throw new DataFormatException(b.toString());
910                }
911                throw new DataFormatException(nextChild + " has no child of type " + theType);
912        }
913
914        protected static <T> List<T> extractMetadataListNotNull(IResource resource, ResourceMetadataKeyEnum<List<T>> key) {
915                List<? extends T> securityLabels = key.get(resource);
916                if (securityLabels == null) {
917                        securityLabels = Collections.emptyList();
918                }
919                return new ArrayList<T>(securityLabels);
920        }
921
922        static boolean hasExtensions(IBase theElement) {
923                if (theElement instanceof ISupportsUndeclaredExtensions) {
924                        ISupportsUndeclaredExtensions res = (ISupportsUndeclaredExtensions) theElement;
925                        if (res.getUndeclaredExtensions().size() > 0 || res.getUndeclaredModifierExtensions().size() > 0) {
926                                return true;
927                        }
928                }
929                if (theElement instanceof IBaseHasExtensions) {
930                        IBaseHasExtensions res = (IBaseHasExtensions) theElement;
931                        if (res.hasExtension()) {
932                                return true;
933                        }
934                }
935                if (theElement instanceof IBaseHasModifierExtensions) {
936                        IBaseHasModifierExtensions res = (IBaseHasModifierExtensions) theElement;
937                        if (res.hasModifierExtension()) {
938                                return true;
939                        }
940                }
941                return false;
942        }
943
944        class ChildNameAndDef {
945
946                private final BaseRuntimeElementDefinition<?> myChildDef;
947                private final String myChildName;
948
949                public ChildNameAndDef(String theChildName, BaseRuntimeElementDefinition<?> theChildDef) {
950                        myChildName = theChildName;
951                        myChildDef = theChildDef;
952                }
953
954                public BaseRuntimeElementDefinition<?> getChildDef() {
955                        return myChildDef;
956                }
957
958                public String getChildName() {
959                        return myChildName;
960                }
961
962        }
963
964        protected class CompositeChildElement {
965                private final BaseRuntimeChildDefinition myDef;
966                private final CompositeChildElement myParent;
967                private final RuntimeResourceDefinition myResDef;
968                private final boolean mySubResource;
969
970                public CompositeChildElement(CompositeChildElement theParent, BaseRuntimeChildDefinition theDef, boolean theSubResource) {
971                        myDef = theDef;
972                        myParent = theParent;
973                        myResDef = null;
974                        mySubResource = theSubResource;
975
976                        if (ourLog.isTraceEnabled()) {
977                                if (theParent != null) {
978                                        StringBuilder path = theParent.buildPath();
979                                        if (path != null) {
980                                                path.append('.');
981                                                path.append(myDef.getElementName());
982                                                ourLog.trace(" * Next path: {}", path.toString());
983                                        }
984                                }
985                        }
986
987                }
988
989                public CompositeChildElement(RuntimeResourceDefinition theResDef, boolean theSubResource) {
990                        myResDef = theResDef;
991                        myDef = null;
992                        myParent = null;
993                        mySubResource = theSubResource;
994                }
995
996                private void addParent(CompositeChildElement theParent, StringBuilder theB) {
997                        if (theParent != null) {
998                                if (theParent.myResDef != null) {
999                                        theB.append(theParent.myResDef.getName());
1000                                        return;
1001                                }
1002
1003                                if (theParent.myParent != null) {
1004                                        addParent(theParent.myParent, theB);
1005                                }
1006
1007                                if (theParent.myDef != null) {
1008                                        if (theB.length() > 0) {
1009                                                theB.append('.');
1010                                        }
1011                                        theB.append(theParent.myDef.getElementName());
1012                                }
1013                        }
1014                }
1015
1016                public boolean anyPathMatches(Set<String> thePaths) {
1017                        StringBuilder b = new StringBuilder();
1018                        addParent(this, b);
1019
1020                        String path = b.toString();
1021                        return thePaths.contains(path);
1022                }
1023
1024                private StringBuilder buildPath() {
1025                        if (myResDef != null) {
1026                                StringBuilder b = new StringBuilder();
1027                                b.append(myResDef.getName());
1028                                return b;
1029                        } else if (myParent != null) {
1030                                StringBuilder b = myParent.buildPath();
1031                                if (b != null && myDef != null) {
1032                                        b.append('.');
1033                                        b.append(myDef.getElementName());
1034                                }
1035                                return b;
1036                        } else {
1037                                return null;
1038                        }
1039                }
1040
1041                private boolean checkIfParentShouldBeEncodedAndBuildPath(StringBuilder thePathBuilder, boolean theStarPass) {
1042                        return checkIfPathMatchesForEncoding(thePathBuilder, theStarPass, myEncodeElementsAppliesToResourceTypes, myEncodeElements, true);
1043                }
1044
1045                private boolean checkIfParentShouldNotBeEncodedAndBuildPath(StringBuilder thePathBuilder, boolean theStarPass) {
1046                        return checkIfPathMatchesForEncoding(thePathBuilder, theStarPass, null, myDontEncodeElements, false);
1047                }
1048
1049                private boolean checkIfPathMatchesForEncoding(StringBuilder thePathBuilder, boolean theStarPass, Set<String> theResourceTypes, Set<String> theElements, boolean theCheckingForWhitelist) {
1050                        if (myResDef != null) {
1051                                if (theResourceTypes != null) {
1052                                        if (!theResourceTypes.contains(myResDef.getName())) {
1053                                                return true;
1054                                        }
1055                                }
1056                                if (theStarPass) {
1057                                        thePathBuilder.append('*');
1058                                } else {
1059                                        thePathBuilder.append(myResDef.getName());
1060                                }
1061                                if (theElements.contains(thePathBuilder.toString())) {
1062                                        return true;
1063                                }
1064                                return false;
1065                        } else if (myParent != null) {
1066                                boolean parentCheck;
1067                                if (theCheckingForWhitelist) {
1068                                        parentCheck = myParent.checkIfParentShouldBeEncodedAndBuildPath(thePathBuilder, theStarPass);
1069                                } else {
1070                                        parentCheck = myParent.checkIfParentShouldNotBeEncodedAndBuildPath(thePathBuilder, theStarPass);
1071                                }
1072                                if (parentCheck) {
1073                                        return true;
1074                                }
1075
1076                                if (myDef != null) {
1077                                        if (myDef.getMin() > 0) {
1078                                                if (theElements.contains("*.(mandatory)")) {
1079                                                        return true;
1080                                                }
1081                                        }
1082
1083                                        thePathBuilder.append('.');
1084                                        thePathBuilder.append(myDef.getElementName());
1085                                        String currentPath = thePathBuilder.toString();
1086                                        boolean retVal = theElements.contains(currentPath);
1087                                        int dotIdx = currentPath.indexOf('.');
1088                                        if (!retVal) {
1089                                                if (dotIdx != -1 && theElements.contains(currentPath.substring(dotIdx + 1))) {
1090                                                        if (!myParent.isSubResource()) {
1091                                                                return true;
1092                                                        }
1093                                                }
1094                                        }
1095                                        return retVal;
1096                                }
1097                        }
1098
1099                        return true;
1100                }
1101
1102                public BaseRuntimeChildDefinition getDef() {
1103                        return myDef;
1104                }
1105
1106                public CompositeChildElement getParent() {
1107                        return myParent;
1108                }
1109
1110                public RuntimeResourceDefinition getResDef() {
1111                        return myResDef;
1112                }
1113
1114                private boolean isSubResource() {
1115                        return mySubResource;
1116                }
1117
1118                public boolean shouldBeEncoded() {
1119                        boolean retVal = true;
1120                        if (myEncodeElements != null) {
1121                                retVal = checkIfParentShouldBeEncodedAndBuildPath(new StringBuilder(), false);
1122                                if (retVal == false && myEncodeElementsIncludesStars) {
1123                                        retVal = checkIfParentShouldBeEncodedAndBuildPath(new StringBuilder(), true);
1124                                }
1125                        }
1126                        if (retVal && myDontEncodeElements != null) {
1127                                retVal = !checkIfParentShouldNotBeEncodedAndBuildPath(new StringBuilder(), false);
1128                                if (retVal && myDontEncodeElementsIncludesStars) {
1129                                        retVal = !checkIfParentShouldNotBeEncodedAndBuildPath(new StringBuilder(), true);
1130                                }
1131                        }
1132                        // if (retVal == false && myEncodeElements.contains("*.(mandatory)")) {
1133                        // if (myDef.getMin() > 0) {
1134                        // retVal = true;
1135                        // }
1136                        // }
1137
1138                        return retVal;
1139                }
1140        }
1141
1142        static class ContainedResources {
1143                private long myNextContainedId = 1;
1144
1145                private List<IBaseResource> myResources = new ArrayList<IBaseResource>();
1146                private IdentityHashMap<IBaseResource, IIdType> myResourceToId = new IdentityHashMap<IBaseResource, IIdType>();
1147
1148                public void addContained(IBaseResource theResource) {
1149                        if (myResourceToId.containsKey(theResource)) {
1150                                return;
1151                        }
1152
1153                        IIdType newId;
1154                        if (theResource.getIdElement().isLocal()) {
1155                                newId = theResource.getIdElement();
1156                        } else {
1157                                // TODO: make this configurable between the two below (and something else?)
1158                                // newId = new IdDt(UUID.randomUUID().toString());
1159                                newId = new IdDt(myNextContainedId++);
1160                        }
1161
1162                        myResourceToId.put(theResource, newId);
1163                        myResources.add(theResource);
1164                }
1165
1166                public void addContained(IIdType theId, IBaseResource theResource) {
1167                        myResourceToId.put(theResource, theId);
1168                        myResources.add(theResource);
1169                }
1170
1171                public List<IBaseResource> getContainedResources() {
1172                        return myResources;
1173                }
1174
1175                public IIdType getResourceId(IBaseResource theNext) {
1176                        return myResourceToId.get(theNext);
1177                }
1178
1179                public boolean isEmpty() {
1180                        return myResourceToId.isEmpty();
1181                }
1182
1183        }
1184
1185}