View Javadoc
1   package ca.uhn.fhir.parser;
2   
3   /*
4    * #%L
5    * HAPI FHIR - Core Library
6    * %%
7    * Copyright (C) 2014 - 2019 University Health Network
8    * %%
9    * Licensed under the Apache License, Version 2.0 (the "License");
10   * you may not use this file except in compliance with the License.
11   * You may obtain a copy of the License at
12   * 
13   * http://www.apache.org/licenses/LICENSE-2.0
14   * 
15   * Unless required by applicable law or agreed to in writing, software
16   * distributed under the License is distributed on an "AS IS" BASIS,
17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   * See the License for the specific language governing permissions and
19   * limitations under the License.
20   * #L%
21   */
22  
23  import ca.uhn.fhir.context.*;
24  import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum;
25  import ca.uhn.fhir.model.api.*;
26  import ca.uhn.fhir.model.primitive.IdDt;
27  import ca.uhn.fhir.rest.api.Constants;
28  import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
29  import ca.uhn.fhir.util.UrlUtil;
30  import com.google.common.base.Charsets;
31  import org.apache.commons.lang3.StringUtils;
32  import org.apache.commons.lang3.Validate;
33  import org.apache.commons.lang3.builder.EqualsBuilder;
34  import org.apache.commons.lang3.builder.HashCodeBuilder;
35  import org.hl7.fhir.instance.model.api.*;
36  
37  import java.io.*;
38  import java.lang.reflect.Modifier;
39  import java.util.*;
40  import java.util.stream.Collectors;
41  
42  import static org.apache.commons.lang3.StringUtils.isBlank;
43  import static org.apache.commons.lang3.StringUtils.isNotBlank;
44  
45  @SuppressWarnings("WeakerAccess")
46  public abstract class BaseParser implements IParser {
47  
48  	private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseParser.class);
49  
50  	private ContainedResources myContainedResources;
51  	private boolean myEncodeElementsAppliesToChildResourcesOnly;
52  	private FhirContext myContext;
53  	private List<ElementsPath> myDontEncodeElements;
54  	private List<ElementsPath> myEncodeElements;
55  	private Set<String> myEncodeElementsAppliesToResourceTypes;
56  	private IIdType myEncodeForceResourceId;
57  	private IParserErrorHandler myErrorHandler;
58  	private boolean myOmitResourceId;
59  	private List<Class<? extends IBaseResource>> myPreferTypes;
60  	private String myServerBaseUrl;
61  	private Boolean myStripVersionsFromReferences;
62  	private Boolean myOverrideResourceIdWithBundleEntryFullUrl;
63  	private boolean mySummaryMode;
64  	private boolean mySuppressNarratives;
65  	private Set<String> myDontStripVersionsFromReferencesAtPaths;
66  
67  	/**
68  	 * Constructor
69  	 */
70  	public BaseParser(FhirContext theContext, IParserErrorHandler theParserErrorHandler) {
71  		myContext = theContext;
72  		myErrorHandler = theParserErrorHandler;
73  	}
74  
75  	protected Iterable<CompositeChildElement> compositeChildIterator(IBase theCompositeElement, final boolean theContainedResource, final CompositeChildElement theParent, EncodeContext theEncodeContext) {
76  
77  		BaseRuntimeElementCompositeDefinition<?> elementDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theCompositeElement.getClass());
78  		final List<BaseRuntimeChildDefinition> children = elementDef.getChildrenAndExtension();
79  
80  		return new Iterable<BaseParser.CompositeChildElement>() {
81  
82  			@Override
83  			public Iterator<CompositeChildElement> iterator() {
84  
85  				return new Iterator<CompositeChildElement>() {
86  					private Iterator<? extends BaseRuntimeChildDefinition> myChildrenIter;
87  					private Boolean myHasNext = null;
88  					private CompositeChildElement myNext;
89  
90  					/**
91  					 * Constructor
92  					 */ {
93  						myChildrenIter = children.iterator();
94  					}
95  
96  					@Override
97  					public boolean hasNext() {
98  						if (myHasNext != null) {
99  							return myHasNext;
100 						}
101 
102 						myNext = null;
103 						do {
104 							if (myChildrenIter.hasNext() == false) {
105 								myHasNext = Boolean.FALSE;
106 								return false;
107 							}
108 
109 							myNext = new CompositeChildElement(theParent, myChildrenIter.next(), theEncodeContext);
110 
111 							/*
112 							 * There are lots of reasons we might skip encoding a particular child
113 							 */
114 							if (myNext.getDef().getElementName().equals("id")) {
115 								myNext = null;
116 							} else if (!myNext.shouldBeEncoded()) {
117 								myNext = null;
118 							} else if (isSummaryMode() && !myNext.getDef().isSummary()) {
119 								myNext = null;
120 							} else if (myNext.getDef() instanceof RuntimeChildNarrativeDefinition) {
121 								if (isSuppressNarratives() || isSummaryMode()) {
122 									myNext = null;
123 								} else if (theContainedResource) {
124 									myNext = null;
125 								}
126 							} else if (myNext.getDef() instanceof RuntimeChildContainedResources) {
127 								if (theContainedResource) {
128 									myNext = null;
129 								}
130 							}
131 
132 						} while (myNext == null);
133 
134 						myHasNext = true;
135 						return true;
136 					}
137 
138 					@Override
139 					public CompositeChildElement next() {
140 						if (myHasNext == null) {
141 							if (!hasNext()) {
142 								throw new IllegalStateException();
143 							}
144 						}
145 						CompositeChildElement retVal = myNext;
146 						myNext = null;
147 						myHasNext = null;
148 						return retVal;
149 					}
150 
151 					@Override
152 					public void remove() {
153 						throw new UnsupportedOperationException();
154 					}
155 				};
156 			}
157 		};
158 	}
159 
160 	private void containResourcesForEncoding(ContainedResources theContained, IBaseResource./org/hl7/fhir/instance/model/api/IBaseResource.html#IBaseResource">IBaseResource theResource, IBaseResource theTarget) {
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 					theContained.getExistingIdToContainedResource().put(nextId, next);
171 				}
172 			}
173 		} else if (theTarget instanceof IDomainResource) {
174 			List<? extends IAnyResource> containedResources = ((IDomainResource) theTarget).getContained();
175 			for (IAnyResource next : containedResources) {
176 				String nextId = next.getIdElement().getValue();
177 				if (StringUtils.isNotBlank(nextId)) {
178 					if (!nextId.startsWith("#")) {
179 						nextId = '#' + nextId;
180 					}
181 					theContained.getExistingIdToContainedResource().put(nextId, next);
182 				}
183 			}
184 		}
185 
186 		List<IBaseReference> allReferences = myContext.newTerser().getAllPopulatedChildElementsOfType(theResource, IBaseReference.class);
187 		for (IBaseReference next : allReferences) {
188 			IBaseResource resource = next.getResource();
189 			if (resource == null && next.getReferenceElement().isLocal()) {
190 				if (theContained.hasExistingIdToContainedResource()) {
191 					IBaseResource potentialTarget = theContained.getExistingIdToContainedResource().remove(next.getReferenceElement().getValue());
192 					if (potentialTarget != null) {
193 						theContained.addContained(next.getReferenceElement(), potentialTarget);
194 						containResourcesForEncoding(theContained, potentialTarget, theTarget);
195 					}
196 				}
197 			}
198 		}
199 
200 		for (IBaseReference next : allReferences) {
201 			IBaseResource resource = next.getResource();
202 			if (resource != null) {
203 				if (resource.getIdElement().isEmpty() || resource.getIdElement().isLocal()) {
204 					if (theContained.getResourceId(resource) != null) {
205 						// Prevent infinite recursion if there are circular loops in the contained resources
206 						continue;
207 					}
208 					theContained.addContained(resource);
209 					if (resource.getIdElement().isLocal() && theContained.hasExistingIdToContainedResource()) {
210 						theContained.getExistingIdToContainedResource().remove(resource.getIdElement().getValue());
211 					}
212 				} else {
213 					continue;
214 				}
215 
216 				containResourcesForEncoding(theContained, resource, theTarget);
217 			}
218 
219 		}
220 
221 	}
222 
223 	protected void containResourcesForEncoding(IBaseResource theResource) {
224 		ContainedResources contained = new ContainedResources();
225 		containResourcesForEncoding(contained, theResource, theResource);
226 		contained.assignIdsToContainedResources();
227 		myContainedResources = contained;
228 
229 	}
230 
231 	private String determineReferenceText(IBaseReference theRef, CompositeChildElement theCompositeChildElement) {
232 		IIdType ref = theRef.getReferenceElement();
233 		if (isBlank(ref.getIdPart())) {
234 			String reference = ref.getValue();
235 			if (theRef.getResource() != null) {
236 				IIdType containedId = getContainedResources().getResourceId(theRef.getResource());
237 				if (containedId != null && !containedId.isEmpty()) {
238 					if (containedId.isLocal()) {
239 						reference = containedId.getValue();
240 					} else {
241 						reference = "#" + containedId.getValue();
242 					}
243 				} else {
244 					IIdType refId = theRef.getResource().getIdElement();
245 					if (refId != null) {
246 						if (refId.hasIdPart()) {
247 							if (refId.getValue().startsWith("urn:")) {
248 								reference = refId.getValue();
249 							} else {
250 								if (!refId.hasResourceType()) {
251 									refId = refId.withResourceType(myContext.getResourceDefinition(theRef.getResource()).getName());
252 								}
253 								if (isStripVersionsFromReferences(theCompositeChildElement)) {
254 									reference = refId.toVersionless().getValue();
255 								} else {
256 									reference = refId.getValue();
257 								}
258 							}
259 						}
260 					}
261 				}
262 			}
263 			return reference;
264 		}
265 		if (!ref.hasResourceType() && !ref.isLocal() && theRef.getResource() != null) {
266 			ref = ref.withResourceType(myContext.getResourceDefinition(theRef.getResource()).getName());
267 		}
268 		if (isNotBlank(myServerBaseUrl) && StringUtils.equals(myServerBaseUrl, ref.getBaseUrl())) {
269 			if (isStripVersionsFromReferences(theCompositeChildElement)) {
270 				return ref.toUnqualifiedVersionless().getValue();
271 			}
272 			return ref.toUnqualified().getValue();
273 		}
274 		if (isStripVersionsFromReferences(theCompositeChildElement)) {
275 			return ref.toVersionless().getValue();
276 		}
277 		return ref.getValue();
278 	}
279 
280 	protected abstract void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter, EncodeContext theEncodeContext) throws IOException, DataFormatException;
281 
282 	protected abstract <T extends IBaseResource> T doParseResource(Class<T> theResourceType, Reader theReader) throws DataFormatException;
283 
284 	@Override
285 	public String encodeResourceToString(IBaseResource theResource) throws DataFormatException {
286 		Writer stringWriter = new StringWriter();
287 		try {
288 			encodeResourceToWriter(theResource, stringWriter);
289 		} catch (IOException e) {
290 			throw new Error("Encountered IOException during write to string - This should not happen!");
291 		}
292 		return stringWriter.toString();
293 	}
294 
295 	@Override
296 	public final void encodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws IOException, DataFormatException {
297 		EncodeContext encodeContext = new EncodeContext();
298 
299 		encodeResourceToWriter(theResource, theWriter, encodeContext);
300 	}
301 
302 	protected void encodeResourceToWriter(IBaseResource theResource, Writer theWriter, EncodeContext theEncodeContext) throws IOException {
303 		Validate.notNull(theResource, "theResource can not be null");
304 		Validate.notNull(theWriter, "theWriter can not be null");
305 		Validate.notNull(theEncodeContext, "theEncodeContext can not be null");
306 
307 		if (theResource.getStructureFhirVersionEnum() != myContext.getVersion().getVersion()) {
308 			throw new IllegalArgumentException(
309 				"This parser is for FHIR version " + myContext.getVersion().getVersion() + " - Can not encode a structure for version " + theResource.getStructureFhirVersionEnum());
310 		}
311 
312 		String resourceName = myContext.getResourceDefinition(theResource).getName();
313 		theEncodeContext.pushPath(resourceName, true);
314 
315 		doEncodeResourceToWriter(theResource, theWriter, theEncodeContext);
316 
317 		theEncodeContext.popPath();
318 	}
319 
320 	private void filterCodingsWithNoCodeOrSystem(List<? extends IBaseCoding> tagList) {
321 		for (int i = 0; i < tagList.size(); i++) {
322 			if (isBlank(tagList.get(i).getCode()) && isBlank(tagList.get(i).getSystem())) {
323 				tagList.remove(i);
324 				i--;
325 			}
326 		}
327 	}
328 
329 	protected IIdType fixContainedResourceId(String theValue) {
330 		IIdType retVal = (IIdType) myContext.getElementDefinition("id").newInstance();
331 		if (StringUtils.isNotBlank(theValue) && theValue.charAt(0) == '#') {
332 			retVal.setValue(theValue.substring(1));
333 		} else {
334 			retVal.setValue(theValue);
335 		}
336 		return retVal;
337 	}
338 
339 	@SuppressWarnings("unchecked")
340 	ChildNameAndDef getChildNameAndDef(BaseRuntimeChildDefinition theChild, IBase theValue) {
341 		Class<? extends IBase> type = theValue.getClass();
342 		String childName = theChild.getChildNameByDatatype(type);
343 		BaseRuntimeElementDefinition<?> childDef = theChild.getChildElementDefinitionByDatatype(type);
344 		if (childDef == null) {
345 			// if (theValue instanceof IBaseExtension) {
346 			// return null;
347 			// }
348 
349 			/*
350 			 * For RI structures Enumeration class, this replaces the child def
351 			 * with the "code" one. This is messy, and presumably there is a better
352 			 * way..
353 			 */
354 			BaseRuntimeElementDefinition<?> elementDef = myContext.getElementDefinition(type);
355 			if (elementDef.getName().equals("code")) {
356 				Class<? extends IBase> type2 = myContext.getElementDefinition("code").getImplementingClass();
357 				childDef = theChild.getChildElementDefinitionByDatatype(type2);
358 				childName = theChild.getChildNameByDatatype(type2);
359 			}
360 
361 			// See possibly the user has extended a built-in type without
362 			// declaring it anywhere, as in XmlParserDstu3Test#testEncodeUndeclaredBlock
363 			if (childDef == null) {
364 				Class<?> nextSuperType = theValue.getClass();
365 				while (IBase.class.isAssignableFrom(nextSuperType) && childDef == null) {
366 					if (Modifier.isAbstract(nextSuperType.getModifiers()) == false) {
367 						BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition((Class<? extends IBase>) nextSuperType);
368 						Class<?> nextChildType = def.getImplementingClass();
369 						childDef = theChild.getChildElementDefinitionByDatatype((Class<? extends IBase>) nextChildType);
370 						childName = theChild.getChildNameByDatatype((Class<? extends IBase>) nextChildType);
371 					}
372 					nextSuperType = nextSuperType.getSuperclass();
373 				}
374 			}
375 
376 			if (childDef == null) {
377 				throwExceptionForUnknownChildType(theChild, type);
378 			}
379 		}
380 
381 		return new ChildNameAndDef(childName, childDef);
382 	}
383 
384 	protected String getCompositeElementId(IBase theElement) {
385 		String elementId = null;
386 		if (!(theElement instanceof IBaseResource)) {
387 			if (theElement instanceof IBaseElement) {
388 				elementId = ((IBaseElement) theElement).getId();
389 			} else if (theElement instanceof IIdentifiableElement) {
390 				elementId = ((IIdentifiableElement) theElement).getElementSpecificId();
391 			}
392 		}
393 		return elementId;
394 	}
395 
396 	ContainedResources getContainedResources() {
397 		return myContainedResources;
398 	}
399 
400 	@Override
401 	public Set<String> getDontStripVersionsFromReferencesAtPaths() {
402 		return myDontStripVersionsFromReferencesAtPaths;
403 	}
404 
405 	@Override
406 	public void setEncodeElements(Set<String> theEncodeElements) {
407 
408 		if (theEncodeElements == null || theEncodeElements.isEmpty()) {
409 			myEncodeElements = null;
410 			myEncodeElementsAppliesToResourceTypes = null;
411 		} else {
412 			myEncodeElements = theEncodeElements
413 				.stream()
414 				.map(ElementsPath::new)
415 				.collect(Collectors.toList());
416 
417 			myEncodeElementsAppliesToResourceTypes = new HashSet<>();
418 			for (String next : myEncodeElements.stream().map(t -> t.getPath().get(0).getName()).collect(Collectors.toList())) {
419 				if (next.startsWith("*")) {
420 					myEncodeElementsAppliesToResourceTypes = null;
421 					break;
422 				}
423 				int dotIdx = next.indexOf('.');
424 				if (dotIdx == -1) {
425 					myEncodeElementsAppliesToResourceTypes.add(next);
426 				} else {
427 					myEncodeElementsAppliesToResourceTypes.add(next.substring(0, dotIdx));
428 				}
429 			}
430 
431 		}
432 	}
433 
434 	@Override
435 	public IIdType getEncodeForceResourceId() {
436 		return myEncodeForceResourceId;
437 	}
438 
439 	@Override
440 	public BaseParser setEncodeForceResourceId(IIdType theEncodeForceResourceId) {
441 		myEncodeForceResourceId = theEncodeForceResourceId;
442 		return this;
443 	}
444 
445 	protected IParserErrorHandler getErrorHandler() {
446 		return myErrorHandler;
447 	}
448 
449 	protected List<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> getExtensionMetadataKeys(IResource resource) {
450 		List<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> extensionMetadataKeys = new ArrayList<>();
451 		for (Map.Entry<ResourceMetadataKeyEnum<?>, Object> entry : resource.getResourceMetadata().entrySet()) {
452 			if (entry.getKey() instanceof ResourceMetadataKeyEnum.ExtensionResourceMetadataKey) {
453 				extensionMetadataKeys.add(entry);
454 			}
455 		}
456 
457 		return extensionMetadataKeys;
458 	}
459 
460 	protected String getExtensionUrl(final String extensionUrl) {
461 		String url = extensionUrl;
462 		if (StringUtils.isNotBlank(extensionUrl) && StringUtils.isNotBlank(myServerBaseUrl)) {
463 			url = !UrlUtil.isValid(extensionUrl) && extensionUrl.startsWith("/") ? myServerBaseUrl + extensionUrl : extensionUrl;
464 		}
465 		return url;
466 	}
467 
468 	protected TagList getMetaTagsForEncoding(IResource theIResource, EncodeContext theEncodeContext) {
469 		TagList tags = ResourceMetadataKeyEnum.TAG_LIST.get(theIResource);
470 		if (shouldAddSubsettedTag(theEncodeContext)) {
471 			tags = new TagList(tags);
472 			tags.add(new Tag(getSubsettedCodeSystem(), Constants.TAG_SUBSETTED_CODE, subsetDescription()));
473 		}
474 
475 		return tags;
476 	}
477 
478 	@Override
479 	public Boolean getOverrideResourceIdWithBundleEntryFullUrl() {
480 		return myOverrideResourceIdWithBundleEntryFullUrl;
481 	}
482 
483 	@Override
484 	public List<Class<? extends IBaseResource>> getPreferTypes() {
485 		return myPreferTypes;
486 	}
487 
488 	@Override
489 	public void setPreferTypes(List<Class<? extends IBaseResource>> thePreferTypes) {
490 		if (thePreferTypes != null) {
491 			ArrayList<Class<? extends IBaseResource>> types = new ArrayList<>();
492 			for (Class<? extends IBaseResource> next : thePreferTypes) {
493 				if (Modifier.isAbstract(next.getModifiers()) == false) {
494 					types.add(next);
495 				}
496 			}
497 			myPreferTypes = Collections.unmodifiableList(types);
498 		} else {
499 			myPreferTypes = thePreferTypes;
500 		}
501 	}
502 
503 	@SuppressWarnings("deprecation")
504 	protected <T extends IPrimitiveType<String>> List<T> getProfileTagsForEncoding(IBaseResource theResource, List<T> theProfiles) {
505 		switch (myContext.getAddProfileTagWhenEncoding()) {
506 			case NEVER:
507 				return theProfiles;
508 			case ONLY_FOR_CUSTOM:
509 				RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource);
510 				if (resDef.isStandardType()) {
511 					return theProfiles;
512 				}
513 				break;
514 			case ALWAYS:
515 				break;
516 		}
517 
518 		RuntimeResourceDefinition nextDef = myContext.getResourceDefinition(theResource);
519 		String profile = nextDef.getResourceProfile(myServerBaseUrl);
520 		if (isNotBlank(profile)) {
521 			for (T next : theProfiles) {
522 				if (profile.equals(next.getValue())) {
523 					return theProfiles;
524 				}
525 			}
526 
527 			List<T> newList = new ArrayList<>(theProfiles);
528 
529 			BaseRuntimeElementDefinition<?> idElement = myContext.getElementDefinition("id");
530 			@SuppressWarnings("unchecked")
531 			T newId = (T) idElement.newInstance();
532 			newId.setValue(profile);
533 
534 			newList.add(newId);
535 			return newList;
536 		}
537 
538 		return theProfiles;
539 	}
540 
541 	protected String getServerBaseUrl() {
542 		return myServerBaseUrl;
543 	}
544 
545 	@Override
546 	public Boolean getStripVersionsFromReferences() {
547 		return myStripVersionsFromReferences;
548 	}
549 
550 	/**
551 	 * If set to <code>true</code> (default is <code>false</code>), narratives will not be included in the encoded
552 	 * values.
553 	 *
554 	 * @deprecated Use {@link #isSuppressNarratives()}
555 	 */
556 	@Deprecated
557 	public boolean getSuppressNarratives() {
558 		return mySuppressNarratives;
559 	}
560 
561 	protected boolean isChildContained(BaseRuntimeElementDefinition<?> childDef, boolean theIncludedResource) {
562 		return (childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCES || childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST) && getContainedResources().isEmpty() == false
563 			&& theIncludedResource == false;
564 	}
565 
566 	@Override
567 	public boolean isEncodeElementsAppliesToChildResourcesOnly() {
568 		return myEncodeElementsAppliesToChildResourcesOnly;
569 	}
570 
571 	@Override
572 	public void setEncodeElementsAppliesToChildResourcesOnly(boolean theEncodeElementsAppliesToChildResourcesOnly) {
573 		myEncodeElementsAppliesToChildResourcesOnly = theEncodeElementsAppliesToChildResourcesOnly;
574 	}
575 
576 	@Override
577 	public boolean isOmitResourceId() {
578 		return myOmitResourceId;
579 	}
580 
581 	private boolean isOverrideResourceIdWithBundleEntryFullUrl() {
582 		Boolean overrideResourceIdWithBundleEntryFullUrl = myOverrideResourceIdWithBundleEntryFullUrl;
583 		if (overrideResourceIdWithBundleEntryFullUrl != null) {
584 			return overrideResourceIdWithBundleEntryFullUrl;
585 		}
586 
587 		return myContext.getParserOptions().isOverrideResourceIdWithBundleEntryFullUrl();
588 	}
589 
590 	private boolean isStripVersionsFromReferences(CompositeChildElement theCompositeChildElement) {
591 		Boolean stripVersionsFromReferences = myStripVersionsFromReferences;
592 		if (stripVersionsFromReferences != null) {
593 			return stripVersionsFromReferences;
594 		}
595 
596 		if (myContext.getParserOptions().isStripVersionsFromReferences() == false) {
597 			return false;
598 		}
599 
600 		Set<String> dontStripVersionsFromReferencesAtPaths = myDontStripVersionsFromReferencesAtPaths;
601 		if (dontStripVersionsFromReferencesAtPaths != null) {
602 			if (dontStripVersionsFromReferencesAtPaths.isEmpty() == false && theCompositeChildElement.anyPathMatches(dontStripVersionsFromReferencesAtPaths)) {
603 				return false;
604 			}
605 		}
606 
607 		dontStripVersionsFromReferencesAtPaths = myContext.getParserOptions().getDontStripVersionsFromReferencesAtPaths();
608 		return dontStripVersionsFromReferencesAtPaths.isEmpty() != false || !theCompositeChildElement.anyPathMatches(dontStripVersionsFromReferencesAtPaths);
609 	}
610 
611 	@Override
612 	public boolean isSummaryMode() {
613 		return mySummaryMode;
614 	}
615 
616 	/**
617 	 * If set to <code>true</code> (default is <code>false</code>), narratives will not be included in the encoded
618 	 * values.
619 	 *
620 	 * @since 1.2
621 	 */
622 	public boolean isSuppressNarratives() {
623 		return mySuppressNarratives;
624 	}
625 
626 	@Override
627 	public IBaseResource parseResource(InputStream theInputStream) throws DataFormatException {
628 		return parseResource(new InputStreamReader(theInputStream, Charsets.UTF_8));
629 	}
630 
631 	@Override
632 	public <T extends IBaseResource> T parseResource(Class<T> theResourceType, InputStream theInputStream) throws DataFormatException {
633 		return parseResource(theResourceType, new InputStreamReader(theInputStream, Charsets.UTF_8));
634 	}
635 
636 	@Override
637 	public <T extends IBaseResource> T parseResource(Class<T> theResourceType, Reader theReader) throws DataFormatException {
638 
639 		/*
640 		 * We do this so that the context can verify that the structure is for
641 		 * the correct FHIR version
642 		 */
643 		if (theResourceType != null) {
644 			myContext.getResourceDefinition(theResourceType);
645 		}
646 
647 		// Actually do the parse
648 		T retVal = doParseResource(theResourceType, theReader);
649 
650 		RuntimeResourceDefinition def = myContext.getResourceDefinition(retVal);
651 		if ("Bundle".equals(def.getName())) {
652 
653 			BaseRuntimeChildDefinition entryChild = def.getChildByName("entry");
654 			BaseRuntimeElementCompositeDefinition<?> entryDef = (BaseRuntimeElementCompositeDefinition<?>) entryChild.getChildByName("entry");
655 			List<IBase> entries = entryChild.getAccessor().getValues(retVal);
656 			if (entries != null) {
657 				for (IBase nextEntry : entries) {
658 
659 					/**
660 					 * If Bundle.entry.fullUrl is populated, set the resource ID to that
661 					 */
662 					// TODO: should emit a warning and maybe notify the error handler if the resource ID doesn't match the
663 					// fullUrl idPart
664 					BaseRuntimeChildDefinition fullUrlChild = entryDef.getChildByName("fullUrl");
665 					if (fullUrlChild == null) {
666 						continue; // TODO: remove this once the data model in tinder plugin catches up to 1.2
667 					}
668 					if (isOverrideResourceIdWithBundleEntryFullUrl()) {
669 						List<IBase> fullUrl = fullUrlChild.getAccessor().getValues(nextEntry);
670 						if (fullUrl != null && !fullUrl.isEmpty()) {
671 							IPrimitiveType<?> value = (IPrimitiveType<?>) fullUrl.get(0);
672 							if (value.isEmpty() == false) {
673 								List<IBase> entryResources = entryDef.getChildByName("resource").getAccessor().getValues(nextEntry);
674 								if (entryResources != null && entryResources.size() > 0) {
675 									IBaseResource res = (IBaseResource) entryResources.get(0);
676 									String versionId = res.getIdElement().getVersionIdPart();
677 									res.setId(value.getValueAsString());
678 									if (isNotBlank(versionId) && res.getIdElement().hasVersionIdPart() == false) {
679 										res.setId(res.getIdElement().withVersion(versionId));
680 									}
681 								}
682 							}
683 						}
684 					}
685 				}
686 			}
687 
688 		}
689 
690 		return retVal;
691 	}
692 
693 	@SuppressWarnings("cast")
694 	@Override
695 	public <T extends IBaseResource> T parseResource(Class<T> theResourceType, String theMessageString) {
696 		StringReader reader = new StringReader(theMessageString);
697 		return parseResource(theResourceType, reader);
698 	}
699 
700 	@Override
701 	public IBaseResource parseResource(Reader theReader) throws ConfigurationException, DataFormatException {
702 		return parseResource(null, theReader);
703 	}
704 
705 	@Override
706 	public IBaseResource parseResource(String theMessageString) throws ConfigurationException, DataFormatException {
707 		return parseResource(null, theMessageString);
708 	}
709 
710 	protected List<? extends IBase> preProcessValues(BaseRuntimeChildDefinition theMetaChildUncast, IBaseResource theResource, List<? extends IBase> theValues,
711 																	 CompositeChildElement theCompositeChildElement, EncodeContext theEncodeContext) {
712 		if (myContext.getVersion().getVersion().isRi()) {
713 
714 			/*
715 			 * If we're encoding the meta tag, we do some massaging of the meta values before
716 			 * encoding. But if there is no meta element at all, we create one since we're possibly going to be
717 			 * adding things to it
718 			 */
719 			if (theValues.isEmpty() && theMetaChildUncast.getElementName().equals("meta")) {
720 				BaseRuntimeElementDefinition<?> metaChild = theMetaChildUncast.getChildByName("meta");
721 				if (IBaseMetaType.class.isAssignableFrom(metaChild.getImplementingClass())) {
722 					IBaseMetaType newType = (IBaseMetaType) metaChild.newInstance();
723 					theValues = Collections.singletonList(newType);
724 				}
725 			}
726 
727 			if (theValues.size() == 1 && theValues.get(0) instanceof IBaseMetaType) {
728 
729 				IBaseMetaType metaValue = (IBaseMetaType) theValues.get(0);
730 				try {
731 					metaValue = (IBaseMetaType) metaValue.getClass().getMethod("copy").invoke(metaValue);
732 				} catch (Exception e) {
733 					throw new InternalErrorException("Failed to duplicate meta", e);
734 				}
735 
736 				if (isBlank(metaValue.getVersionId())) {
737 					if (theResource.getIdElement().hasVersionIdPart()) {
738 						metaValue.setVersionId(theResource.getIdElement().getVersionIdPart());
739 					}
740 				}
741 
742 				filterCodingsWithNoCodeOrSystem(metaValue.getTag());
743 				filterCodingsWithNoCodeOrSystem(metaValue.getSecurity());
744 
745 				List<? extends IPrimitiveType<String>> newProfileList = getProfileTagsForEncoding(theResource, metaValue.getProfile());
746 				List<? extends IPrimitiveType<String>> oldProfileList = metaValue.getProfile();
747 				if (oldProfileList != newProfileList) {
748 					oldProfileList.clear();
749 					for (IPrimitiveType<String> next : newProfileList) {
750 						if (isNotBlank(next.getValue())) {
751 							metaValue.addProfile(next.getValue());
752 						}
753 					}
754 				}
755 
756 				if (shouldAddSubsettedTag(theEncodeContext)) {
757 					IBaseCoding coding = metaValue.addTag();
758 					coding.setCode(Constants.TAG_SUBSETTED_CODE);
759 					coding.setSystem(getSubsettedCodeSystem());
760 					coding.setDisplay(subsetDescription());
761 				}
762 
763 				return Collections.singletonList(metaValue);
764 			}
765 		}
766 
767 		@SuppressWarnings("unchecked")
768 		List<IBase> retVal = (List<IBase>) theValues;
769 
770 		for (int i = 0; i < retVal.size(); i++) {
771 			IBase next = retVal.get(i);
772 
773 			/*
774 			 * If we have automatically contained any resources via
775 			 * their references, this ensures that we output the new
776 			 * local reference
777 			 */
778 			if (next instanceof IBaseReference) {
779 				IBaseReference nextRef = (IBaseReference) next;
780 				String refText = determineReferenceText(nextRef, theCompositeChildElement);
781 				if (!StringUtils.equals(refText, nextRef.getReferenceElement().getValue())) {
782 
783 					if (retVal == theValues) {
784 						retVal = new ArrayList<>(theValues);
785 					}
786 					IBaseReference newRef = (IBaseReference) myContext.getElementDefinition(nextRef.getClass()).newInstance();
787 					myContext.newTerser().cloneInto(nextRef, newRef, true);
788 					newRef.setReference(refText);
789 					retVal.set(i, newRef);
790 
791 				}
792 			}
793 		}
794 
795 		return retVal;
796 	}
797 
798 	private String getSubsettedCodeSystem() {
799 		if (myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) {
800 			return Constants.TAG_SUBSETTED_SYSTEM_R4;
801 		} else {
802 			return Constants.TAG_SUBSETTED_SYSTEM_DSTU3;
803 		}
804 	}
805 
806 	@Override
807 	public void setDontEncodeElements(Set<String> theDontEncodeElements) {
808 		if (theDontEncodeElements == null || theDontEncodeElements.isEmpty()) {
809 			myDontEncodeElements = null;
810 		} else {
811 			myDontEncodeElements = theDontEncodeElements
812 				.stream()
813 				.map(ElementsPath::new)
814 				.collect(Collectors.toList());
815 		}
816 	}
817 
818 	@Override
819 	public IParser setDontStripVersionsFromReferencesAtPaths(String... thePaths) {
820 		if (thePaths == null) {
821 			setDontStripVersionsFromReferencesAtPaths((List<String>) null);
822 		} else {
823 			setDontStripVersionsFromReferencesAtPaths(Arrays.asList(thePaths));
824 		}
825 		return this;
826 	}
827 
828 	@SuppressWarnings("unchecked")
829 	@Override
830 	public IParser setDontStripVersionsFromReferencesAtPaths(Collection<String> thePaths) {
831 		if (thePaths == null) {
832 			myDontStripVersionsFromReferencesAtPaths = Collections.emptySet();
833 		} else if (thePaths instanceof HashSet) {
834 			myDontStripVersionsFromReferencesAtPaths = (Set<String>) ((HashSet<String>) thePaths).clone();
835 		} else {
836 			myDontStripVersionsFromReferencesAtPaths = new HashSet<>(thePaths);
837 		}
838 		return this;
839 	}
840 
841 	@Override
842 	public IParser setOmitResourceId(boolean theOmitResourceId) {
843 		myOmitResourceId = theOmitResourceId;
844 		return this;
845 	}
846 
847 	@Override
848 	public IParser setOverrideResourceIdWithBundleEntryFullUrl(Boolean theOverrideResourceIdWithBundleEntryFullUrl) {
849 		myOverrideResourceIdWithBundleEntryFullUrl = theOverrideResourceIdWithBundleEntryFullUrl;
850 		return this;
851 	}
852 
853 	@Override
854 	public IParser setParserErrorHandler(IParserErrorHandler theErrorHandler) {
855 		Validate.notNull(theErrorHandler, "theErrorHandler must not be null");
856 		myErrorHandler = theErrorHandler;
857 		return this;
858 	}
859 
860 	@Override
861 	public IParser setServerBaseUrl(String theUrl) {
862 		myServerBaseUrl = isNotBlank(theUrl) ? theUrl : null;
863 		return this;
864 	}
865 
866 	@Override
867 	public IParser setStripVersionsFromReferences(Boolean theStripVersionsFromReferences) {
868 		myStripVersionsFromReferences = theStripVersionsFromReferences;
869 		return this;
870 	}
871 
872 	@Override
873 	public IParser setSummaryMode(boolean theSummaryMode) {
874 		mySummaryMode = theSummaryMode;
875 		return this;
876 	}
877 
878 	@Override
879 	public IParser setSuppressNarratives(boolean theSuppressNarratives) {
880 		mySuppressNarratives = theSuppressNarratives;
881 		return this;
882 	}
883 
884 	protected boolean shouldAddSubsettedTag(EncodeContext theEncodeContext) {
885 		if (isSummaryMode()) {
886 			return true;
887 		}
888 		if (isSuppressNarratives()) {
889 			return true;
890 		}
891 		if (myEncodeElements != null) {
892 			if (isEncodeElementsAppliesToChildResourcesOnly() && theEncodeContext.getResourcePath().size() < 2) {
893 				return false;
894 			}
895 
896 			String currentResourceName = theEncodeContext.getResourcePath().get(theEncodeContext.getResourcePath().size() - 1).getName();
897 			if (myEncodeElementsAppliesToResourceTypes == null || myEncodeElementsAppliesToResourceTypes.contains(currentResourceName)) {
898 				return true;
899 			}
900 		}
901 
902 		return false;
903 	}
904 
905 	protected boolean shouldEncodeResourceId(IBaseResource theResource, EncodeContext theEncodeContext) {
906 		boolean retVal = true;
907 		if (isOmitResourceId()) {
908 			retVal = false;
909 		} else {
910 			if (myDontEncodeElements != null) {
911 				String resourceName = myContext.getResourceDefinition(theResource).getName();
912 				if (myDontEncodeElements.stream().anyMatch(t -> t.equalsPath(resourceName + ".id"))) {
913 					retVal = false;
914 				} else if (myDontEncodeElements.stream().anyMatch(t -> t.equalsPath("*.id"))) {
915 					retVal = false;
916 				} else if (theEncodeContext.getResourcePath().size() == 1 && myDontEncodeElements.stream().anyMatch(t -> t.equalsPath("id"))) {
917 					retVal = false;
918 				}
919 			}
920 		}
921 		return retVal;
922 	}
923 
924 	/**
925 	 * Used for DSTU2 only
926 	 */
927 	protected boolean shouldEncodeResourceMeta(IResource theResource) {
928 		return shouldEncodePath(theResource, "meta");
929 	}
930 
931 	/**
932 	 * Used for DSTU2 only
933 	 */
934 	protected boolean shouldEncodePath(IResource theResource, String thePath) {
935 		if (myDontEncodeElements != null) {
936 			String resourceName = myContext.getResourceDefinition(theResource).getName();
937 			if (myDontEncodeElements.stream().anyMatch(t -> t.equalsPath(resourceName + "." + thePath))) {
938 				return false;
939 			} else if (myDontEncodeElements.stream().anyMatch(t -> t.equalsPath("*."+ thePath))) {
940 				return false;
941 			}
942 		}
943 		return true;
944 	}
945 
946 	private String subsetDescription() {
947 		return "Resource encoded in summary mode";
948 	}
949 
950 	protected void throwExceptionForUnknownChildType(BaseRuntimeChildDefinition nextChild, Class<? extends IBase> theType) {
951 		if (nextChild instanceof BaseRuntimeDeclaredChildDefinition) {
952 			StringBuilder b = new StringBuilder();
953 			b.append(nextChild.getElementName());
954 			b.append(" has type ");
955 			b.append(theType.getName());
956 			b.append(" but this is not a valid type for this element");
957 			if (nextChild instanceof RuntimeChildChoiceDefinition) {
958 				RuntimeChildChoiceDefinition choice = (RuntimeChildChoiceDefinition) nextChild;
959 				b.append(" - Expected one of: " + choice.getValidChildTypes());
960 			}
961 			throw new DataFormatException(b.toString());
962 		}
963 		throw new DataFormatException(nextChild + " has no child of type " + theType);
964 	}
965 
966 	class ChildNameAndDef {
967 
968 		private final BaseRuntimeElementDefinition<?> myChildDef;
969 		private final String myChildName;
970 
971 		public ChildNameAndDef(String theChildName, BaseRuntimeElementDefinition<?> theChildDef) {
972 			myChildName = theChildName;
973 			myChildDef = theChildDef;
974 		}
975 
976 		public BaseRuntimeElementDefinition<?> getChildDef() {
977 			return myChildDef;
978 		}
979 
980 		public String getChildName() {
981 			return myChildName;
982 		}
983 
984 	}
985 
986 	protected class CompositeChildElement {
987 		private final BaseRuntimeChildDefinition myDef;
988 		private final CompositeChildElement myParent;
989 		private final RuntimeResourceDefinition myResDef;
990 		private final EncodeContext myEncodeContext;
991 
992 		public CompositeChildElement(CompositeChildElement theParent, BaseRuntimeChildDefinition theDef, EncodeContext theEncodeContext) {
993 			myDef = theDef;
994 			myParent = theParent;
995 			myResDef = null;
996 			myEncodeContext = theEncodeContext;
997 
998 			if (ourLog.isTraceEnabled()) {
999 				if (theParent != null) {
1000 					StringBuilder path = theParent.buildPath();
1001 					if (path != null) {
1002 						path.append('.');
1003 						path.append(myDef.getElementName());
1004 						ourLog.trace(" * Next path: {}", path.toString());
1005 					}
1006 				}
1007 			}
1008 
1009 		}
1010 
1011 		public CompositeChildElement(RuntimeResourceDefinition theResDef, EncodeContext theEncodeContext) {
1012 			myResDef = theResDef;
1013 			myDef = null;
1014 			myParent = null;
1015 			myEncodeContext = theEncodeContext;
1016 		}
1017 
1018 		private void addParent(CompositeChildElement theParent, StringBuilder theB) {
1019 			if (theParent != null) {
1020 				if (theParent.myResDef != null) {
1021 					theB.append(theParent.myResDef.getName());
1022 					return;
1023 				}
1024 
1025 				if (theParent.myParent != null) {
1026 					addParent(theParent.myParent, theB);
1027 				}
1028 
1029 				if (theParent.myDef != null) {
1030 					if (theB.length() > 0) {
1031 						theB.append('.');
1032 					}
1033 					theB.append(theParent.myDef.getElementName());
1034 				}
1035 			}
1036 		}
1037 
1038 		public boolean anyPathMatches(Set<String> thePaths) {
1039 			StringBuilder b = new StringBuilder();
1040 			addParent(this, b);
1041 
1042 			String path = b.toString();
1043 			return thePaths.contains(path);
1044 		}
1045 
1046 		private StringBuilder buildPath() {
1047 			if (myResDef != null) {
1048 				StringBuilder b = new StringBuilder();
1049 				b.append(myResDef.getName());
1050 				return b;
1051 			} else if (myParent != null) {
1052 				StringBuilder b = myParent.buildPath();
1053 				if (b != null && myDef != null) {
1054 					b.append('.');
1055 					b.append(myDef.getElementName());
1056 				}
1057 				return b;
1058 			} else {
1059 				return null;
1060 			}
1061 		}
1062 
1063 		private boolean checkIfParentShouldBeEncodedAndBuildPath() {
1064 			List<ElementsPath> encodeElements = myEncodeElements;
1065 
1066 			String currentResourceName = myEncodeContext.getResourcePath().get(myEncodeContext.getResourcePath().size() - 1).getName();
1067 			if (myEncodeElementsAppliesToResourceTypes != null && !myEncodeElementsAppliesToResourceTypes.contains(currentResourceName)) {
1068 				encodeElements = null;
1069 			}
1070 
1071 			boolean retVal = checkIfPathMatchesForEncoding(encodeElements, true);
1072 
1073 			/*
1074 			 * We force the meta tag to be encoded even if it's not specified as an element in the
1075 			 * elements filter, specifically because we'll need it in order to automatically add
1076 			 * the SUBSETTED tag
1077 			 */
1078 			if (!retVal) {
1079 				if ("meta".equals(myEncodeContext.getLeafResourcePathFirstField()) && shouldAddSubsettedTag(myEncodeContext)) {
1080 					// The next element is a child of the <meta> element
1081 					retVal = true;
1082 				} else if ("meta".equals(myDef.getElementName()) && shouldAddSubsettedTag(myEncodeContext)) {
1083 					// The next element is the <meta> element
1084 					retVal = true;
1085 				}
1086 			}
1087 
1088 			return retVal;
1089 		}
1090 
1091 		private boolean checkIfParentShouldNotBeEncodedAndBuildPath() {
1092 			return checkIfPathMatchesForEncoding(myDontEncodeElements, false);
1093 		}
1094 
1095 		private boolean checkIfPathMatchesForEncoding(List<ElementsPath> theElements, boolean theCheckingForEncodeElements) {
1096 
1097 			boolean retVal = false;
1098 			myEncodeContext.pushPath(myDef.getElementName(), false);
1099 
1100 			if (theCheckingForEncodeElements && isEncodeElementsAppliesToChildResourcesOnly() && myEncodeContext.getResourcePath().size() < 2) {
1101 				retVal = true;
1102 			} else if (theElements == null) {
1103 				retVal = true;
1104 			} else {
1105 				EncodeContextPath currentResourcePath = myEncodeContext.getCurrentResourcePath();
1106 				ourLog.trace("Current resource path: {}", currentResourcePath);
1107 				for (ElementsPath next : theElements) {
1108 
1109 					if (next.startsWith(currentResourcePath)) {
1110 						if (theCheckingForEncodeElements || next.getPath().size() == currentResourcePath.getPath().size()) {
1111 							retVal = true;
1112 							break;
1113 						}
1114 					}
1115 
1116 					if (next.getPath().get(next.getPath().size() - 1).getName().equals("(mandatory)")) {
1117 						if (myDef.getMin() > 0) {
1118 							retVal = true;
1119 							break;
1120 						}
1121 						if (currentResourcePath.getPath().size() > next.getPath().size()) {
1122 							retVal = true;
1123 							break;
1124 						}
1125 					}
1126 
1127 				}
1128 			}
1129 
1130 			myEncodeContext.popPath();
1131 			return retVal;
1132 		}
1133 
1134 		public BaseRuntimeChildDefinition getDef() {
1135 			return myDef;
1136 		}
1137 
1138 		public CompositeChildElement getParent() {
1139 			return myParent;
1140 		}
1141 
1142 		public boolean shouldBeEncoded() {
1143 			boolean retVal = true;
1144 			if (myEncodeElements != null) {
1145 				retVal = checkIfParentShouldBeEncodedAndBuildPath();
1146 			}
1147 			if (retVal && myDontEncodeElements != null) {
1148 				retVal = !checkIfParentShouldNotBeEncodedAndBuildPath();
1149 			}
1150 
1151 			return retVal;
1152 		}
1153 	}
1154 
1155 	protected class EncodeContextPath {
1156 		private final List<EncodeContextPathElement> myPath;
1157 
1158 		public EncodeContextPath() {
1159 			myPath = new ArrayList<>(10);
1160 		}
1161 
1162 		public EncodeContextPath(List<EncodeContextPathElement> thePath) {
1163 			myPath = thePath;
1164 		}
1165 
1166 		@Override
1167 		public String toString() {
1168 			return myPath.toString();
1169 		}
1170 
1171 		protected List<EncodeContextPathElement> getPath() {
1172 			return myPath;
1173 		}
1174 
1175 		public EncodeContextPath getCurrentResourcePath() {
1176 			EncodeContextPath retVal = null;
1177 			for (int i = myPath.size() - 1; i >= 0; i--) {
1178 				if (myPath.get(i).isResource()) {
1179 					retVal = new EncodeContextPath(myPath.subList(i, myPath.size()));
1180 					break;
1181 				}
1182 			}
1183 			Validate.isTrue(retVal != null);
1184 			return retVal;
1185 		}
1186 	}
1187 
1188 	protected class ElementsPath extends EncodeContextPath {
1189 
1190 		protected ElementsPath(String thePath) {
1191 			StringTokenizer tok = new StringTokenizer(thePath, ".");
1192 			boolean first = true;
1193 			while (tok.hasMoreTokens()) {
1194 				String next = tok.nextToken();
1195 				if (first && next.equals("*")) {
1196 					getPath().add(new EncodeContextPathElement("*", true));
1197 				} else if (isNotBlank(next)) {
1198 					getPath().add(new EncodeContextPathElement(next, Character.isUpperCase(next.charAt(0))));
1199 				}
1200 				first = false;
1201 			}
1202 		}
1203 
1204 		public boolean startsWith(EncodeContextPath theCurrentResourcePath) {
1205 			for (int i = 0; i < getPath().size(); i++) {
1206 				if (theCurrentResourcePath.getPath().size() == i) {
1207 					return true;
1208 				}
1209 				EncodeContextPathElement expected = getPath().get(i);
1210 				EncodeContextPathElement actual = theCurrentResourcePath.getPath().get(i);
1211 				if (!expected.matches(actual)) {
1212 					return false;
1213 				}
1214 			}
1215 			return true;
1216 		}
1217 
1218 		public boolean equalsPath(String thePath) {
1219 			ElementsPath parsedPath = new ElementsPath(thePath);
1220 			return getPath().equals(parsedPath.getPath());
1221 		}
1222 	}
1223 
1224 
1225 	/**
1226 	 * EncodeContext is a shared state object that is passed around the
1227 	 * encode process
1228 	 */
1229 	protected class EncodeContext extends EncodeContextPath {
1230 		private final ArrayList<EncodeContextPathElement> myResourcePath = new ArrayList<>(10);
1231 
1232 		protected ArrayList<EncodeContextPathElement> getResourcePath() {
1233 			return myResourcePath;
1234 		}
1235 
1236 		public String getLeafResourcePathFirstField() {
1237 			String retVal = null;
1238 			for (int i = getPath().size() - 1; i >= 0; i--) {
1239 				if (getPath().get(i).isResource()) {
1240 					break;
1241 				} else {
1242 					retVal = getPath().get(i).getName();
1243 				}
1244 			}
1245 			return retVal;
1246 		}
1247 
1248 
1249 		/**
1250 		 * Add an element at the end of the path
1251 		 */
1252 		protected void pushPath(String thePathElement, boolean theResource) {
1253 			assert isNotBlank(thePathElement);
1254 			assert !thePathElement.contains(".");
1255 			assert theResource ^ Character.isLowerCase(thePathElement.charAt(0));
1256 
1257 			EncodeContextPathElement element = new EncodeContextPathElement(thePathElement, theResource);
1258 			getPath().add(element);
1259 			if (theResource) {
1260 				myResourcePath.add(element);
1261 			}
1262 		}
1263 
1264 		/**
1265 		 * Remove the element at the end of the path
1266 		 */
1267 		public void popPath() {
1268 			EncodeContextPathElement removed = getPath().remove(getPath().size() - 1);
1269 			if (removed.isResource()) {
1270 				myResourcePath.remove(myResourcePath.size() - 1);
1271 			}
1272 		}
1273 
1274 
1275 	}
1276 
1277 	protected class EncodeContextPathElement {
1278 		private final String myName;
1279 		private final boolean myResource;
1280 
1281 		public EncodeContextPathElement(String theName, boolean theResource) {
1282 			Validate.notBlank(theName);
1283 			myName = theName;
1284 			myResource = theResource;
1285 		}
1286 
1287 
1288 		public boolean matches(EncodeContextPathElement theOther) {
1289 			if (myResource != theOther.isResource()) {
1290 				return false;
1291 			}
1292 			if (myName.equals(theOther.getName())) {
1293 				return true;
1294 			}
1295 			if (myName.equals("*")) {
1296 				return true;
1297 			}
1298 			return false;
1299 		}
1300 
1301 		@Override
1302 		public boolean equals(Object theO) {
1303 			if (this == theO) {
1304 				return true;
1305 			}
1306 
1307 			if (theO == null || getClass() != theO.getClass()) {
1308 				return false;
1309 			}
1310 
1311 			EncodeContextPathElement that = (EncodeContextPathElement) theO;
1312 
1313 			return new EqualsBuilder()
1314 				.append(myResource, that.myResource)
1315 				.append(myName, that.myName)
1316 				.isEquals();
1317 		}
1318 
1319 		@Override
1320 		public int hashCode() {
1321 			return new HashCodeBuilder(17, 37)
1322 				.append(myName)
1323 				.append(myResource)
1324 				.toHashCode();
1325 		}
1326 
1327 		@Override
1328 		public String toString() {
1329 			if (myResource) {
1330 				return myName + "(res)";
1331 			}
1332 			return myName;
1333 		}
1334 
1335 		public String getName() {
1336 			return myName;
1337 		}
1338 
1339 		public boolean isResource() {
1340 			return myResource;
1341 		}
1342 	}
1343 
1344 	static class ContainedResources {
1345 		private long myNextContainedId = 1;
1346 
1347 		private List<IBaseResource> myResourceList;
1348 		private IdentityHashMap<IBaseResource, IIdType> myResourceToIdMap;
1349 		private Map<String, IBaseResource> myExistingIdToContainedResourceMap;
1350 
1351 		public Map<String, IBaseResource> getExistingIdToContainedResource() {
1352 			if (myExistingIdToContainedResourceMap == null) {
1353 				myExistingIdToContainedResourceMap = new HashMap<>();
1354 			}
1355 			return myExistingIdToContainedResourceMap;
1356 		}
1357 
1358 		public void addContained(IBaseResource theResource) {
1359 			if (getResourceToIdMap().containsKey(theResource)) {
1360 				return;
1361 			}
1362 
1363 			IIdType newId;
1364 			if (theResource.getIdElement().isLocal()) {
1365 				newId = theResource.getIdElement();
1366 			} else {
1367 				newId = null;
1368 			}
1369 
1370 			getResourceToIdMap().put(theResource, newId);
1371 			getResourceList().add(theResource);
1372 		}
1373 
1374 		public void addContained(IIdType theId, IBaseResource theResource) {
1375 			if (!getResourceToIdMap().containsKey(theResource)) {
1376 				getResourceToIdMap().put(theResource, theId);
1377 				getResourceList().add(theResource);
1378 			}
1379 		}
1380 
1381 		public List<IBaseResource> getContainedResources() {
1382 			if (getResourceToIdMap() == null) {
1383 				return Collections.emptyList();
1384 			}
1385 			return getResourceList();
1386 		}
1387 
1388 		public IIdType getResourceId(IBaseResource theNext) {
1389 			if (getResourceToIdMap() == null) {
1390 				return null;
1391 			}
1392 			return getResourceToIdMap().get(theNext);
1393 		}
1394 
1395 		private List<IBaseResource> getResourceList() {
1396 			if (myResourceList == null) {
1397 				myResourceList = new ArrayList<>();
1398 			}
1399 			return myResourceList;
1400 		}
1401 
1402 		private IdentityHashMap<IBaseResource, IIdType> getResourceToIdMap() {
1403 			if (myResourceToIdMap == null) {
1404 				myResourceToIdMap = new IdentityHashMap<>();
1405 			}
1406 			return myResourceToIdMap;
1407 		}
1408 
1409 		public boolean isEmpty() {
1410 			if (myResourceToIdMap == null) {
1411 				return true;
1412 			}
1413 			return myResourceToIdMap.isEmpty();
1414 		}
1415 
1416 		public boolean hasExistingIdToContainedResource() {
1417 			return myExistingIdToContainedResourceMap != null;
1418 		}
1419 
1420 		public void assignIdsToContainedResources() {
1421 
1422 			if (getResourceList() != null) {
1423 
1424 				/*
1425 				 * The idea with the code block below:
1426 				 *
1427 				 * We want to preserve any IDs that were user-assigned, so that if it's really
1428 				 * important to someone that their contained resource have the ID of #FOO
1429 				 * or #1 we will keep that.
1430 				 *
1431 				 * For any contained resources where no ID was assigned by the user, we
1432 				 * want to manually create an ID but make sure we don't reuse an existing ID.
1433 				 */
1434 
1435 				Set<String> ids = new HashSet<>();
1436 
1437 				// Gather any user assigned IDs
1438 				for (IBaseResource nextResource : getResourceList()) {
1439 					if (getResourceToIdMap().get(nextResource) != null) {
1440 						ids.add(getResourceToIdMap().get(nextResource).getValue());
1441 					}
1442 				}
1443 
1444 				// Automatically assign IDs to the rest
1445 				for (IBaseResource nextResource : getResourceList()) {
1446 
1447 					while (getResourceToIdMap().get(nextResource) == null) {
1448 						String nextCandidate = "#" + myNextContainedId;
1449 						myNextContainedId++;
1450 						if (!ids.add(nextCandidate)) {
1451 							continue;
1452 						}
1453 
1454 						getResourceToIdMap().put(nextResource, new IdDt(nextCandidate));
1455 					}
1456 
1457 				}
1458 
1459 			}
1460 
1461 		}
1462 	}
1463 
1464 	protected static <T> List<T> extractMetadataListNotNull(IResource resource, ResourceMetadataKeyEnum<List<T>> key) {
1465 		List<? extends T> securityLabels = key.get(resource);
1466 		if (securityLabels == null) {
1467 			securityLabels = Collections.emptyList();
1468 		}
1469 		return new ArrayList<>(securityLabels);
1470 	}
1471 
1472 	static boolean hasNoExtensions(IBase theElement) {
1473 		if (theElement instanceof ISupportsUndeclaredExtensions) {
1474 			ISupportsUndeclaredExtensions res = (ISupportsUndeclaredExtensions) theElement;
1475 			if (res.getUndeclaredExtensions().size() > 0 || res.getUndeclaredModifierExtensions().size() > 0) {
1476 				return false;
1477 			}
1478 		}
1479 		if (theElement instanceof IBaseHasExtensions) {
1480 			IBaseHasExtensions res = (IBaseHasExtensions) theElement;
1481 			if (res.hasExtension()) {
1482 				return false;
1483 			}
1484 		}
1485 		if (theElement instanceof IBaseHasModifierExtensions) {
1486 			IBaseHasModifierExtensions res = (IBaseHasModifierExtensions) theElement;
1487 			return !res.hasModifierExtension();
1488 		}
1489 		return true;
1490 	}
1491 
1492 }