001package ca.uhn.fhir.parser;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2016 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 */
022import static org.apache.commons.lang3.StringUtils.isBlank;
023import static org.apache.commons.lang3.StringUtils.isNotBlank;
024
025import java.io.IOException;
026import java.io.Reader;
027import java.io.StringReader;
028import java.io.StringWriter;
029import java.io.Writer;
030import java.util.ArrayList;
031import java.util.Collections;
032import java.util.HashMap;
033import java.util.HashSet;
034import java.util.IdentityHashMap;
035import java.util.Iterator;
036import java.util.List;
037import java.util.Map;
038import java.util.Set;
039
040import org.apache.commons.lang3.StringUtils;
041import org.apache.commons.lang3.Validate;
042import org.hl7.fhir.instance.model.api.IAnyResource;
043import org.hl7.fhir.instance.model.api.IBase;
044import org.hl7.fhir.instance.model.api.IBaseCoding;
045import org.hl7.fhir.instance.model.api.IBaseMetaType;
046import org.hl7.fhir.instance.model.api.IBaseReference;
047import org.hl7.fhir.instance.model.api.IBaseResource;
048import org.hl7.fhir.instance.model.api.IDomainResource;
049import org.hl7.fhir.instance.model.api.IIdType;
050import org.hl7.fhir.instance.model.api.IPrimitiveType;
051
052import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
053import ca.uhn.fhir.context.BaseRuntimeDeclaredChildDefinition;
054import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
055import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
056import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum;
057import ca.uhn.fhir.context.ConfigurationException;
058import ca.uhn.fhir.context.FhirContext;
059import ca.uhn.fhir.context.FhirVersionEnum;
060import ca.uhn.fhir.context.RuntimeChildChoiceDefinition;
061import ca.uhn.fhir.context.RuntimeChildContainedResources;
062import ca.uhn.fhir.context.RuntimeChildNarrativeDefinition;
063import ca.uhn.fhir.context.RuntimeResourceDefinition;
064import ca.uhn.fhir.model.api.Bundle;
065import ca.uhn.fhir.model.api.BundleEntry;
066import ca.uhn.fhir.model.api.IResource;
067import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
068import ca.uhn.fhir.model.api.Tag;
069import ca.uhn.fhir.model.api.TagList;
070import ca.uhn.fhir.model.primitive.IdDt;
071import ca.uhn.fhir.rest.server.Constants;
072import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
073
074public abstract class BaseParser implements IParser {
075
076        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseParser.class);
077        private ContainedResources myContainedResources;
078        private FhirContext myContext;
079        private Set<String> myEncodeElements;
080        private Set<String> myEncodeElementsAppliesToResourceTypes;
081        private boolean myEncodeElementsIncludesStars;
082        private IParserErrorHandler myErrorHandler;
083        private boolean myOmitResourceId;
084        private String myServerBaseUrl;
085        private boolean myStripVersionsFromReferences = true;
086        private boolean mySummaryMode;
087        private boolean mySuppressNarratives;
088
089        /**
090         * Constructor
091         * 
092         * @param theParserErrorHandler
093         */
094        public BaseParser(FhirContext theContext, IParserErrorHandler theParserErrorHandler) {
095                myContext = theContext;
096                myErrorHandler = theParserErrorHandler;
097        }
098
099        protected Iterable<CompositeChildElement> compositeChildIterator(final List<? extends BaseRuntimeChildDefinition> theChildren, final boolean theContainedResource, final CompositeChildElement theParent) {
100                return new Iterable<BaseParser.CompositeChildElement>() {
101                        @Override
102                        public Iterator<CompositeChildElement> iterator() {
103
104                                return new Iterator<CompositeChildElement>() {
105                                        private Iterator<? extends BaseRuntimeChildDefinition> myChildrenIter;
106                                        private Boolean myHasNext = null;
107                                        private CompositeChildElement myNext;
108
109                                        /**
110                                         * Constructor
111                                         */
112                                        {
113                                                myChildrenIter = theChildren.iterator();
114                                        }
115
116                                        @Override
117                                        public boolean hasNext() {
118                                                if (myHasNext != null) {
119                                                        return myHasNext;
120                                                }
121
122                                                myNext = null;
123                                                do {
124                                                        if (myChildrenIter.hasNext() == false) {
125                                                                myHasNext = Boolean.FALSE;
126                                                                return false;
127                                                        }
128
129                                                        myNext = new CompositeChildElement(theParent, myChildrenIter.next());
130
131                                                        /*
132                                                         * There are lots of reasons we might skip encoding a particular child
133                                                         */
134                                                        if (myNext.getDef().getElementName().equals("extension") || myNext.getDef().getElementName().equals("modifierExtension")) {
135                                                                myNext = null;
136                                                        } else if (myNext.getDef().getElementName().equals("id")) {
137                                                                myNext = null;
138                                                        } else if (!myNext.shouldBeEncoded()) {
139                                                                myNext = null;
140                                                        } else if (isSummaryMode() && !myNext.getDef().isSummary()) {
141                                                                myNext = null;
142                                                        } else if (myNext.getDef() instanceof RuntimeChildNarrativeDefinition) {
143                                                                if (isSuppressNarratives() || isSummaryMode()) {
144                                                                        myNext = null;
145                                                                } else if (theContainedResource) {
146                                                                        myNext = null;
147                                                                }
148                                                        } else if (myNext.getDef() instanceof RuntimeChildContainedResources) {
149                                                                if (theContainedResource) {
150                                                                        myNext = null;
151                                                                }
152                                                        }
153
154                                                } while (myNext == null);
155
156                                                myHasNext = true;
157                                                return true;
158                                        }
159
160                                        @Override
161                                        public CompositeChildElement next() {
162                                                if (myHasNext == null) {
163                                                        if (!hasNext()) {
164                                                                throw new IllegalStateException();
165                                                        }
166                                                }
167                                                CompositeChildElement retVal = myNext;
168                                                myNext = null;
169                                                myHasNext = null;
170                                                return retVal;
171                                        }
172
173                                        @Override
174                                        public void remove() {
175                                                throw new UnsupportedOperationException();
176                                        }
177                                };
178                        }
179                };
180        }
181
182        private void containResourcesForEncoding(ContainedResources theContained, IBaseResource theResource, IBaseResource theTarget) {
183                Set<String> allIds = new HashSet<String>();
184                Map<String, IBaseResource> existingIdToContainedResource = null;
185
186                if (theTarget instanceof IResource) {
187                        List<? extends IResource> containedResources = ((IResource) theTarget).getContained().getContainedResources();
188                        for (IResource next : containedResources) {
189                                String nextId = next.getId().getValue();
190                                if (StringUtils.isNotBlank(nextId)) {
191                                        if (!nextId.startsWith("#")) {
192                                                nextId = '#' + nextId;
193                                        }
194                                        allIds.add(nextId);
195                                        if (existingIdToContainedResource == null) {
196                                                existingIdToContainedResource = new HashMap<String, IBaseResource>();
197                                        }
198                                        existingIdToContainedResource.put(nextId, next);
199                                }
200                        }
201                } else if (theTarget instanceof IDomainResource) {
202                        List<? extends IAnyResource> containedResources = ((IDomainResource) theTarget).getContained();
203                        for (IAnyResource next : containedResources) {
204                                String nextId = next.getIdElement().getValue();
205                                if (StringUtils.isNotBlank(nextId)) {
206                                        if (!nextId.startsWith("#")) {
207                                                nextId = '#' + nextId;
208                                        }
209                                        allIds.add(nextId);
210                                        if (existingIdToContainedResource == null) {
211                                                existingIdToContainedResource = new HashMap<String, IBaseResource>();
212                                        }
213                                        existingIdToContainedResource.put(nextId, next);
214                                }
215                        }
216                } else {
217                        // no resources to contain
218                }
219
220                {
221                        List<IBaseReference> allElements = myContext.newTerser().getAllPopulatedChildElementsOfType(theResource, IBaseReference.class);
222                        for (IBaseReference next : allElements) {
223                                IBaseResource resource = next.getResource();
224                                if (resource != null) {
225                                        if (resource.getIdElement().isEmpty() || resource.getIdElement().isLocal()) {
226                                                if (theContained.getResourceId(resource) != null) {
227                                                        // Prevent infinite recursion if there are circular loops in the contained resources
228                                                        continue;
229                                                }
230                                                theContained.addContained(resource);
231                                        } else {
232                                                continue;
233                                        }
234
235                                        containResourcesForEncoding(theContained, resource, theTarget);
236                                } else if (next.getReferenceElement().isLocal()) {
237                                        if (existingIdToContainedResource != null) {
238                                                IBaseResource potentialTarget = existingIdToContainedResource.remove(next.getReferenceElement().getValue());
239                                                if (potentialTarget != null) {
240                                                        theContained.addContained(next.getReferenceElement(), potentialTarget);
241                                                        containResourcesForEncoding(theContained, potentialTarget, theTarget);
242                                                }
243                                        }
244                                }
245                        }
246                }
247
248        }
249
250        protected void containResourcesForEncoding(IBaseResource theResource) {
251                ContainedResources contained = new ContainedResources();
252                containResourcesForEncoding(contained, theResource, theResource);
253                myContainedResources = contained;
254        }
255
256        protected String determineReferenceText(IBaseReference theRef) {
257                IIdType ref = theRef.getReferenceElement();
258                if (isBlank(ref.getIdPart())) {
259                        String reference = ref.getValue();
260                        if (theRef.getResource() != null) {
261                                IIdType containedId = getContainedResources().getResourceId(theRef.getResource());
262                                if (containedId != null && !containedId.isEmpty()) {
263                                        if (containedId.isLocal()) {
264                                                reference = containedId.getValue();
265                                        } else {
266                                                reference = "#" + containedId.getValue();
267                                        }
268                                } else {
269                                        IIdType refId = theRef.getResource().getIdElement();
270                                        if (refId != null) {
271                                                if (refId.hasIdPart()) {
272                                                        if (!refId.hasResourceType()) {
273                                                                refId = refId.withResourceType(myContext.getResourceDefinition(theRef.getResource()).getName());
274                                                        }
275                                                        if (isStripVersionsFromReferences()) {
276                                                                reference = refId.toVersionless().getValue();
277                                                        } else {
278                                                                reference = refId.getValue();
279                                                        }
280                                                }
281                                        }
282                                }
283                        }
284                        return reference;
285                } else {
286                        if (!ref.hasResourceType() && !ref.isLocal() && theRef.getResource() != null) {
287                                ref = ref.withResourceType(myContext.getResourceDefinition(theRef.getResource()).getName());
288                        }
289                        if (isNotBlank(myServerBaseUrl) && StringUtils.equals(myServerBaseUrl, ref.getBaseUrl())) {
290                                if (isStripVersionsFromReferences()) {
291                                        return ref.toUnqualifiedVersionless().getValue();
292                                } else {
293                                        return ref.toUnqualified().getValue();
294                                }
295                        } else {
296                                if (isStripVersionsFromReferences()) {
297                                        return ref.toVersionless().getValue();
298                                } else {
299                                        return ref.getValue();
300                                }
301                        }
302                }
303        }
304
305        protected String determineResourceBaseUrl(String bundleBaseUrl, BundleEntry theEntry) {
306                IResource resource = theEntry.getResource();
307                if (resource == null) {
308                        return null;
309                }
310
311                String resourceBaseUrl = null;
312                if (resource.getId() != null && resource.getId().hasBaseUrl()) {
313                        if (!resource.getId().getBaseUrl().equals(bundleBaseUrl)) {
314                                resourceBaseUrl = resource.getId().getBaseUrl();
315                        }
316                }
317                return resourceBaseUrl;
318        }
319
320        protected abstract void doEncodeBundleToWriter(Bundle theBundle, Writer theWriter) throws IOException, DataFormatException;
321
322        protected abstract void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws IOException, DataFormatException;
323
324        protected abstract <T extends IBaseResource> T doParseResource(Class<T> theResourceType, Reader theReader) throws DataFormatException;
325
326        @Override
327        public String encodeBundleToString(Bundle theBundle) throws DataFormatException {
328                if (theBundle == null) {
329                        throw new NullPointerException("Bundle can not be null");
330                }
331                StringWriter stringWriter = new StringWriter();
332                try {
333                        encodeBundleToWriter(theBundle, stringWriter);
334                } catch (IOException e) {
335                        throw new Error("Encountered IOException during write to string - This should not happen!");
336                }
337
338                return stringWriter.toString();
339        }
340
341        @Override
342        public final void encodeBundleToWriter(Bundle theBundle, Writer theWriter) throws IOException, DataFormatException {
343                Validate.notNull(theBundle, "theBundle must not be null");
344                Validate.notNull(theWriter, "theWriter must not be null");
345                doEncodeBundleToWriter(theBundle, theWriter);
346        }
347
348        @Override
349        public String encodeResourceToString(IBaseResource theResource) throws DataFormatException {
350                Writer stringWriter = new StringWriter();
351                try {
352                        encodeResourceToWriter(theResource, stringWriter);
353                } catch (IOException e) {
354                        throw new Error("Encountered IOException during write to string - This should not happen!");
355                }
356                return stringWriter.toString();
357        }
358
359        @Override
360        public final void encodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws IOException, DataFormatException {
361                Validate.notNull(theResource, "theResource can not be null");
362                Validate.notNull(theWriter, "theWriter can not be null");
363
364                doEncodeResourceToWriter(theResource, theWriter);
365        }
366
367        @Override
368        public String encodeTagListToString(TagList theTagList) {
369                Writer stringWriter = new StringWriter();
370                try {
371                        encodeTagListToWriter(theTagList, stringWriter);
372                } catch (IOException e) {
373                        throw new Error("Encountered IOException during write to string - This should not happen!");
374                }
375                return stringWriter.toString();
376        }
377
378        private void filterCodingsWithNoCodeOrSystem(List<? extends IBaseCoding> tagList) {
379                for (int i = 0; i < tagList.size(); i++) {
380                        if (isBlank(tagList.get(i).getCode()) && isBlank(tagList.get(i).getSystem())) {
381                                tagList.remove(i);
382                                i--;
383                        }
384                }
385        }
386
387        protected String fixContainedResourceId(String theValue) {
388                if (StringUtils.isNotBlank(theValue) && theValue.charAt(0) == '#') {
389                        return theValue.substring(1);
390                }
391                return theValue;
392        }
393
394        ContainedResources getContainedResources() {
395                return myContainedResources;
396        }
397
398        /**
399         * See {@link #setEncodeElements(Set)}
400         */
401        @Override
402        public Set<String> getEncodeElements() {
403                return myEncodeElements;
404        }
405
406        /**
407         * See {@link #setEncodeElementsAppliesToResourceTypes(Set)}
408         */
409        @Override
410        public Set<String> getEncodeElementsAppliesToResourceTypes() {
411                return myEncodeElementsAppliesToResourceTypes;
412        }
413
414        protected IParserErrorHandler getErrorHandler() {
415                return myErrorHandler;
416        }
417
418        protected TagList getMetaTagsForEncoding(IResource theIResource) {
419                TagList tags = ResourceMetadataKeyEnum.TAG_LIST.get(theIResource);
420                if (shouldAddSubsettedTag()) {
421                        tags = new TagList(tags);
422                        tags.add(new Tag(Constants.TAG_SUBSETTED_SYSTEM, Constants.TAG_SUBSETTED_CODE, subsetDescription()));
423                }
424
425                return tags;
426        }
427
428        /**
429         * If set to <code>true</code> (default is <code>false</code>), narratives will not be included in the encoded
430         * values.
431         * 
432         * @deprecated Use {@link #isSuppressNarratives()}
433         */
434        @Deprecated
435        public boolean getSuppressNarratives() {
436                return mySuppressNarratives;
437        }
438
439        protected boolean isChildContained(BaseRuntimeElementDefinition<?> childDef, boolean theIncludedResource) {
440                return (childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCES || childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST) && getContainedResources().isEmpty() == false && theIncludedResource == false;
441        }
442
443        @Override
444        public boolean isOmitResourceId() {
445                return myOmitResourceId;
446        }
447
448        @Override
449        public boolean isStripVersionsFromReferences() {
450                return myStripVersionsFromReferences;
451        }
452
453        @Override
454        public boolean isSummaryMode() {
455                return mySummaryMode;
456        }
457
458        /**
459         * If set to <code>true</code> (default is <code>false</code>), narratives will not be included in the encoded
460         * values.
461         * 
462         * @since 1.2
463         */
464        public boolean isSuppressNarratives() {
465                return mySuppressNarratives;
466        }
467
468        @Override
469        public Bundle parseBundle(Reader theReader) {
470                if (myContext.getVersion().getVersion() == FhirVersionEnum.DSTU2_HL7ORG) {
471                        throw new IllegalStateException("Can't parse DSTU1 (Atom) bundle in HL7.org DSTU2 mode. Use parseResource(Bundle.class, foo) instead.");
472                }
473                return parseBundle(null, theReader);
474        }
475
476        @Override
477        public Bundle parseBundle(String theXml) throws ConfigurationException, DataFormatException {
478                StringReader reader = new StringReader(theXml);
479                return parseBundle(reader);
480        }
481
482        @Override
483        public <T extends IBaseResource> T parseResource(Class<T> theResourceType, Reader theReader) throws DataFormatException {
484                T retVal = doParseResource(theResourceType, theReader);
485
486                RuntimeResourceDefinition def = myContext.getResourceDefinition(retVal);
487                if ("Bundle".equals(def.getName())) {
488
489                        BaseRuntimeChildDefinition entryChild = def.getChildByName("entry");
490                        BaseRuntimeElementCompositeDefinition<?> entryDef = (BaseRuntimeElementCompositeDefinition<?>) entryChild.getChildByName("entry");
491                        List<IBase> entries = entryChild.getAccessor().getValues(retVal);
492                        if (entries != null) {
493                                for (IBase nextEntry : entries) {
494
495                                        /**
496                                         * If Bundle.entry.fullUrl is populated, set the resource ID to that
497                                         */
498                                        // TODO: should emit a warning and maybe notify the error handler if the resource ID doesn't match the
499                                        // fullUrl idPart
500                                        BaseRuntimeChildDefinition fullUrlChild = entryDef.getChildByName("fullUrl");
501                                        if (fullUrlChild == null) {
502                                                continue; // TODO: remove this once the data model in tinder plugin catches up to 1.2
503                                        }
504                                        List<IBase> fullUrl = fullUrlChild.getAccessor().getValues(nextEntry);
505                                        if (fullUrl != null && !fullUrl.isEmpty()) {
506                                                IPrimitiveType<?> value = (IPrimitiveType<?>) fullUrl.get(0);
507                                                if (value.isEmpty() == false) {
508                                                        List<IBase> entryResources = entryDef.getChildByName("resource").getAccessor().getValues(nextEntry);
509                                                        if (entryResources != null && entryResources.size() > 0) {
510                                                                IBaseResource res = (IBaseResource) entryResources.get(0);
511                                                                String versionId = res.getIdElement().getVersionIdPart();
512                                                                res.setId(value.getValueAsString());
513                                                                if (isNotBlank(versionId) && res.getIdElement().hasVersionIdPart() == false) {
514                                                                        res.setId(res.getIdElement().withVersion(versionId));
515                                                                }
516                                                        }
517                                                }
518                                        }
519
520                                }
521                        }
522
523                }
524
525                return retVal;
526        }
527
528        @SuppressWarnings("cast")
529        @Override
530        public <T extends IBaseResource> T parseResource(Class<T> theResourceType, String theMessageString) {
531                StringReader reader = new StringReader(theMessageString);
532                return (T) parseResource(theResourceType, reader);
533        }
534
535        @Override
536        public IBaseResource parseResource(Reader theReader) throws ConfigurationException, DataFormatException {
537                return parseResource(null, theReader);
538        }
539
540        @Override
541        public IBaseResource parseResource(String theMessageString) throws ConfigurationException, DataFormatException {
542                return parseResource(null, theMessageString);
543        }
544
545        @Override
546        public TagList parseTagList(String theString) {
547                return parseTagList(new StringReader(theString));
548        }
549
550        @SuppressWarnings("cast")
551        protected List<? extends IBase> preProcessValues(BaseRuntimeChildDefinition metaChildUncast, IBaseResource theResource, List<? extends IBase> theValues) {
552                if (myContext.getVersion().getVersion().isRi()) {
553                        
554                        /*
555                         * If we're encoding the meta tag, we do some massaging of the meta values before
556                         * encoding. Buf if there is no meta element at all, we create one since we're possibly going to be
557                         * adding things to it
558                         */
559                        if (theValues.isEmpty() && metaChildUncast.getElementName().equals("meta")) {
560                                BaseRuntimeElementDefinition<?> metaChild = metaChildUncast.getChildByName("meta");
561                                if (IBaseMetaType.class.isAssignableFrom(metaChild.getImplementingClass())) {
562                                        IBaseMetaType newType = (IBaseMetaType) metaChild.newInstance();
563                                        theValues = Collections.singletonList(newType);
564                                }
565                        }
566                        
567                        if (theValues.size() == 1 && theValues.get(0) instanceof IBaseMetaType) {
568                                
569                                IBaseMetaType metaValue = (IBaseMetaType) theValues.get(0);
570                                try {
571                                        metaValue = (IBaseMetaType) metaValue.getClass().getMethod("copy").invoke(metaValue);
572                                } catch (Exception e) {
573                                        throw new InternalErrorException("Failed to duplicate meta", e);
574                                }
575                                
576                                if (isBlank(metaValue.getVersionId())) {
577                                        if (theResource.getIdElement().hasVersionIdPart()) {
578                                                metaValue.setVersionId(theResource.getIdElement().getVersionIdPart());
579                                        }
580                                }
581                                
582                                filterCodingsWithNoCodeOrSystem(metaValue.getTag());
583                                filterCodingsWithNoCodeOrSystem(metaValue.getSecurity());
584                                
585                                if (shouldAddSubsettedTag()) {
586                                        IBaseCoding coding = metaValue.addTag();
587                                        coding.setCode(Constants.TAG_SUBSETTED_CODE);
588                                        coding.setSystem(Constants.TAG_SUBSETTED_SYSTEM);
589                                        coding.setDisplay(subsetDescription());
590                                }
591                                
592                                return Collections.singletonList(metaValue);
593                        }
594                }
595                
596                return theValues;
597        }
598
599        @Override
600        public void setEncodeElements(Set<String> theEncodeElements) {
601                myEncodeElementsIncludesStars = false;
602                if (theEncodeElements == null || theEncodeElements.isEmpty()) {
603                        myEncodeElements = null;
604                } else {
605                        myEncodeElements = theEncodeElements;
606                        for (String next : theEncodeElements) {
607                                if (next.startsWith("*.")) {
608                                        myEncodeElementsIncludesStars = true;
609                                }
610                        }
611                }
612        }
613
614        @Override
615        public void setEncodeElementsAppliesToResourceTypes(Set<String> theEncodeElementsAppliesToResourceTypes) {
616                if (theEncodeElementsAppliesToResourceTypes == null || theEncodeElementsAppliesToResourceTypes.isEmpty()) {
617                        myEncodeElementsAppliesToResourceTypes = null;
618                } else {
619                        myEncodeElementsAppliesToResourceTypes = theEncodeElementsAppliesToResourceTypes;
620                }
621        }
622
623        @Override
624        public IParser setOmitResourceId(boolean theOmitResourceId) {
625                myOmitResourceId = theOmitResourceId;
626                return this;
627        }
628
629        @Override
630        public IParser setParserErrorHandler(IParserErrorHandler theErrorHandler) {
631                Validate.notNull(theErrorHandler, "theErrorHandler must not be null");
632                myErrorHandler = theErrorHandler;
633                return this;
634        }
635
636        @Override
637        public IParser setServerBaseUrl(String theUrl) {
638                myServerBaseUrl = isNotBlank(theUrl) ? theUrl : null;
639                return this;
640        }
641
642        @Override
643        public IParser setStripVersionsFromReferences(boolean theStripVersionsFromReferences) {
644                myStripVersionsFromReferences = theStripVersionsFromReferences;
645                return this;
646        }
647
648        @Override
649        public IParser setSummaryMode(boolean theSummaryMode) {
650                mySummaryMode = theSummaryMode;
651                return this;
652        }
653
654        @Override
655        public IParser setSuppressNarratives(boolean theSuppressNarratives) {
656                mySuppressNarratives = theSuppressNarratives;
657                return this;
658        }
659
660        protected boolean shouldAddSubsettedTag() {
661                return isSummaryMode() || isSuppressNarratives();
662        }
663
664        private String subsetDescription() {
665                return "Resource encoded in summary mode";
666        }
667
668        protected void throwExceptionForUnknownChildType(BaseRuntimeChildDefinition nextChild, Class<? extends IBase> theType) {
669                if (nextChild instanceof BaseRuntimeDeclaredChildDefinition) {
670                        StringBuilder b = new StringBuilder();
671                        b.append(((BaseRuntimeDeclaredChildDefinition) nextChild).getElementName());
672                        b.append(" has type ");
673                        b.append(theType.getName());
674                        b.append(" but this is not a valid type for this element");
675                        if (nextChild instanceof RuntimeChildChoiceDefinition) {
676                                RuntimeChildChoiceDefinition choice = (RuntimeChildChoiceDefinition) nextChild;
677                                b.append(" - Expected one of: " + choice.getValidChildTypes());
678                        }
679                        throw new DataFormatException(b.toString());
680                }
681                throw new DataFormatException(nextChild + " has no child of type " + theType);
682        }
683
684        protected static <T> List<T> extractMetadataListNotNull(IResource resource, ResourceMetadataKeyEnum<List<T>> key) {
685                List<T> securityLabels = key.get(resource);
686                if (securityLabels == null) {
687                        securityLabels = Collections.emptyList();
688                }
689                return securityLabels;
690        }
691
692        protected class CompositeChildElement {
693                private final BaseRuntimeChildDefinition myDef;
694                private final CompositeChildElement myParent;
695                private final RuntimeResourceDefinition myResDef;
696
697                public CompositeChildElement(CompositeChildElement theParent, BaseRuntimeChildDefinition theDef) {
698                        myDef = theDef;
699                        myParent = theParent;
700                        myResDef = null;
701
702                        if (ourLog.isTraceEnabled()) {
703                                if (theParent != null) {
704                                        StringBuilder path = theParent.buildPath();
705                                        if (path != null) {
706                                                path.append('.');
707                                                path.append(myDef.getElementName());
708                                                ourLog.trace(" * Next path: {}", path.toString());
709                                        }
710                                }
711                        }
712
713                }
714
715
716                public CompositeChildElement(RuntimeResourceDefinition theResDef) {
717                        myResDef = theResDef;
718                        myDef = null;
719                        myParent = null;
720                }
721
722                private StringBuilder buildPath() {
723                        if (myResDef != null) {
724                                StringBuilder b = new StringBuilder();
725                                b.append(myResDef.getName());
726                                return b;
727                        } else {
728                                StringBuilder b = myParent.buildPath();
729                                if (b != null && myDef != null) {
730                                        b.append('.');
731                                        b.append(myDef.getElementName());
732                                }
733                                return b;
734                        }
735                }
736
737                private boolean checkIfParentShouldBeEncodedAndBuildPath(StringBuilder theB, boolean theStarPass) {
738                        if (myResDef != null) {
739                                if (myEncodeElementsAppliesToResourceTypes != null) {
740                                        if (!myEncodeElementsAppliesToResourceTypes.contains(myResDef.getName())) {
741                                                return true;
742                                        }
743                                }
744                                if (theStarPass) {
745                                        theB.append('*');
746                                } else {
747                                        theB.append(myResDef.getName());
748                                }
749                                if (myEncodeElements.contains(theB.toString())) {
750                                        return true;
751                                } else {
752                                        return false;
753                                }
754                        } else if (myParent != null) {
755                                if (myParent.checkIfParentShouldBeEncodedAndBuildPath(theB, theStarPass)) {
756                                        return true;
757                                }
758
759                                if (myDef != null) {
760                                        theB.append('.');
761                                        theB.append(myDef.getElementName());
762                                        return myEncodeElements.contains(theB.toString());
763                                }
764                        }
765
766                        return true;
767                }
768
769                public BaseRuntimeChildDefinition getDef() {
770                        return myDef;
771                }
772
773                public CompositeChildElement getParent() {
774                        return myParent;
775                }
776
777                public RuntimeResourceDefinition getResDef() {
778                        return myResDef;
779                }
780
781                public boolean shouldBeEncoded() {
782                        if (myEncodeElements == null) {
783                                return true;
784                        }
785                        boolean retVal = checkIfParentShouldBeEncodedAndBuildPath(new StringBuilder(), false);
786                        if (retVal == false && myEncodeElementsIncludesStars) {
787                                retVal = checkIfParentShouldBeEncodedAndBuildPath(new StringBuilder(), true);
788                        }
789                        return retVal;
790                }
791        }
792
793        static class ContainedResources {
794                private long myNextContainedId = 1;
795
796                private List<IBaseResource> myResources = new ArrayList<IBaseResource>();
797                private IdentityHashMap<IBaseResource, IIdType> myResourceToId = new IdentityHashMap<IBaseResource, IIdType>();
798
799                public void addContained(IBaseResource theResource) {
800                        if (myResourceToId.containsKey(theResource)) {
801                                return;
802                        }
803
804                        IIdType newId;
805                        if (theResource.getIdElement().isLocal()) {
806                                newId = theResource.getIdElement();
807                        } else {
808                                // TODO: make this configurable between the two below (and something else?)
809                                // newId = new IdDt(UUID.randomUUID().toString());
810                                newId = new IdDt(myNextContainedId++);
811                        }
812
813                        myResourceToId.put(theResource, newId);
814                        myResources.add(theResource);
815                }
816
817                public void addContained(IIdType theId, IBaseResource theResource) {
818                        myResourceToId.put(theResource, theId);
819                        myResources.add(theResource);
820                }
821
822                public List<IBaseResource> getContainedResources() {
823                        return myResources;
824                }
825
826                public IIdType getResourceId(IBaseResource theNext) {
827                        return myResourceToId.get(theNext);
828                }
829
830                public boolean isEmpty() {
831                        return myResourceToId.isEmpty();
832                }
833
834        }
835
836}