001package ca.uhn.fhir.util;
002
003import ca.uhn.fhir.context.*;
004import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum;
005import ca.uhn.fhir.model.api.ExtensionDt;
006import ca.uhn.fhir.model.api.IResource;
007import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions;
008import ca.uhn.fhir.model.base.composite.BaseContainedDt;
009import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
010import ca.uhn.fhir.model.primitive.StringDt;
011import ca.uhn.fhir.parser.DataFormatException;
012import org.apache.commons.lang3.Validate;
013import org.hl7.fhir.instance.model.api.*;
014
015import java.util.*;
016import java.util.regex.Matcher;
017import java.util.regex.Pattern;
018import java.util.stream.Collectors;
019
020import static org.apache.commons.lang3.StringUtils.*;
021
022/*
023 * #%L
024 * HAPI FHIR - Core Library
025 * %%
026 * Copyright (C) 2014 - 2019 University Health Network
027 * %%
028 * Licensed under the Apache License, Version 2.0 (the "License");
029 * you may not use this file except in compliance with the License.
030 * You may obtain a copy of the License at
031 *
032 *      http://www.apache.org/licenses/LICENSE-2.0
033 *
034 * Unless required by applicable law or agreed to in writing, software
035 * distributed under the License is distributed on an "AS IS" BASIS,
036 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
037 * See the License for the specific language governing permissions and
038 * limitations under the License.
039 * #L%
040 */
041
042public class FhirTerser {
043
044        private static final Pattern COMPARTMENT_MATCHER_PATH = Pattern.compile("([a-zA-Z.]+)\\.where\\(resolve\\(\\) is ([a-zA-Z]+)\\)");
045        private FhirContext myContext;
046
047        public FhirTerser(FhirContext theContext) {
048                super();
049                myContext = theContext;
050        }
051
052        private List<String> addNameToList(List<String> theCurrentList, BaseRuntimeChildDefinition theChildDefinition) {
053                if (theChildDefinition == null)
054                        return null;
055                if (theCurrentList == null || theCurrentList.isEmpty())
056                        return new ArrayList<>(Collections.singletonList(theChildDefinition.getElementName()));
057                List<String> newList = new ArrayList<>(theCurrentList);
058                newList.add(theChildDefinition.getElementName());
059                return newList;
060        }
061
062        private ExtensionDt createEmptyExtensionDt(IBaseExtension theBaseExtension, String theUrl) {
063                return createEmptyExtensionDt(theBaseExtension, false, theUrl);
064        }
065
066        @SuppressWarnings("unchecked")
067        private ExtensionDt createEmptyExtensionDt(IBaseExtension theBaseExtension, boolean theIsModifier, String theUrl) {
068                ExtensionDt retVal = new ExtensionDt(theIsModifier, theUrl);
069                theBaseExtension.getExtension().add(retVal);
070                return retVal;
071        }
072
073        private ExtensionDt createEmptyExtensionDt(ISupportsUndeclaredExtensions theSupportsUndeclaredExtensions, String theUrl) {
074                return createEmptyExtensionDt(theSupportsUndeclaredExtensions, false, theUrl);
075        }
076
077        private ExtensionDt createEmptyExtensionDt(ISupportsUndeclaredExtensions theSupportsUndeclaredExtensions, boolean theIsModifier, String theUrl) {
078                return theSupportsUndeclaredExtensions.addUndeclaredExtension(theIsModifier, theUrl);
079        }
080
081        private IBaseExtension createEmptyExtension(IBaseHasExtensions theBaseHasExtensions, String theUrl) {
082                return (IBaseExtension) theBaseHasExtensions.addExtension().setUrl(theUrl);
083        }
084
085        private IBaseExtension createEmptyModifierExtension(IBaseHasModifierExtensions theBaseHasModifierExtensions, String theUrl) {
086                return (IBaseExtension) theBaseHasModifierExtensions.addModifierExtension().setUrl(theUrl);
087        }
088
089        private ExtensionDt createEmptyModifierExtensionDt(ISupportsUndeclaredExtensions theSupportsUndeclaredExtensions, String theUrl) {
090                return createEmptyExtensionDt(theSupportsUndeclaredExtensions, true, theUrl);
091        }
092
093        /**
094         * Clones all values from a source object into the equivalent fields in a target object
095         *
096         * @param theSource              The source object (must not be null)
097         * @param theTarget              The target object to copy values into (must not be null)
098         * @param theIgnoreMissingFields The ignore fields in the target which do not exist (if false, an exception will be thrown if the target is unable to accept a value from the source)
099         * @return Returns the target (which will be the same object that was passed into theTarget) for easy chaining
100         */
101        public IBase cloneInto(IBase theSource, IBase theTarget, boolean theIgnoreMissingFields) {
102                Validate.notNull(theSource, "theSource must not be null");
103                Validate.notNull(theTarget, "theTarget must not be null");
104
105                if (theSource instanceof IPrimitiveType<?>) {
106                        if (theTarget instanceof IPrimitiveType<?>) {
107                                ((IPrimitiveType<?>) theTarget).setValueAsString(((IPrimitiveType<?>) theSource).getValueAsString());
108                                return theSource;
109                        }
110                        if (theIgnoreMissingFields) {
111                                return theSource;
112                        }
113                        throw new DataFormatException("Can not copy value from primitive of type " + theSource.getClass().getName() + " into type " + theTarget.getClass().getName());
114                }
115
116                BaseRuntimeElementCompositeDefinition<?> sourceDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theSource.getClass());
117                BaseRuntimeElementCompositeDefinition<?> targetDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theTarget.getClass());
118
119                List<BaseRuntimeChildDefinition> children = sourceDef.getChildren();
120                if (sourceDef instanceof RuntimeExtensionDtDefinition) {
121                        children = ((RuntimeExtensionDtDefinition) sourceDef).getChildrenIncludingUrl();
122                }
123
124                for (BaseRuntimeChildDefinition nextChild : children)
125                        for (IBase nextValue : nextChild.getAccessor().getValues(theSource)) {
126                                String elementName = nextChild.getChildNameByDatatype(nextValue.getClass());
127                                BaseRuntimeChildDefinition targetChild = targetDef.getChildByName(elementName);
128                                if (targetChild == null) {
129                                        if (theIgnoreMissingFields) {
130                                                continue;
131                                        }
132                                        throw new DataFormatException("Type " + theTarget.getClass().getName() + " does not have a child with name " + elementName);
133                                }
134
135                                BaseRuntimeElementDefinition<?> element = myContext.getElementDefinition(nextValue.getClass());
136                                IBase target = element.newInstance();
137
138                                targetChild.getMutator().addValue(theTarget, target);
139                                cloneInto(nextValue, target, theIgnoreMissingFields);
140                        }
141
142                return theTarget;
143        }
144
145        /**
146         * Returns a list containing all child elements (including the resource itself) which are <b>non-empty</b> and are either of the exact type specified, or are a subclass of that type.
147         * <p>
148         * For example, specifying a type of {@link StringDt} would return all non-empty string instances within the message. Specifying a type of {@link IResource} would return the resource itself, as
149         * well as any contained resources.
150         * </p>
151         * <p>
152         * Note on scope: This method will descend into any contained resources ({@link IResource#getContained()}) as well, but will not descend into linked resources (e.g.
153         * {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource)
154         * </p>
155         *
156         * @param theResource The resource instance to search. Must not be null.
157         * @param theType     The type to search for. Must not be null.
158         * @return Returns a list of all matching elements
159         */
160        public <T extends IBase> List<T> getAllPopulatedChildElementsOfType(IBaseResource theResource, final Class<T> theType) {
161                final ArrayList<T> retVal = new ArrayList<>();
162                BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource);
163                visit(new IdentityHashMap<>(), theResource, theResource, null, null, def, new IModelVisitor() {
164                        @SuppressWarnings("unchecked")
165                        @Override
166                        public void acceptElement(IBaseResource theOuterResource, IBase theElement, List<String> thePathToElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition<?> theDefinition) {
167                                if (theElement == null || theElement.isEmpty()) {
168                                        return;
169                                }
170
171                                if (theType.isAssignableFrom(theElement.getClass())) {
172                                        retVal.add((T) theElement);
173                                }
174                        }
175                });
176                return retVal;
177        }
178
179        public List<ResourceReferenceInfo> getAllResourceReferences(final IBaseResource theResource) {
180                final ArrayList<ResourceReferenceInfo> retVal = new ArrayList<>();
181                BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource);
182                visit(new IdentityHashMap<>(), theResource, theResource, null, null, def, new IModelVisitor() {
183                        @Override
184                        public void acceptElement(IBaseResource theOuterResource, IBase theElement, List<String> thePathToElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition<?> theDefinition) {
185                                if (theElement == null || theElement.isEmpty()) {
186                                        return;
187                                }
188                                if (IBaseReference.class.isAssignableFrom(theElement.getClass())) {
189                                        retVal.add(new ResourceReferenceInfo(myContext, theOuterResource, thePathToElement, (IBaseReference) theElement));
190                                }
191                        }
192                });
193                return retVal;
194        }
195
196        private BaseRuntimeChildDefinition getDefinition(BaseRuntimeElementCompositeDefinition<?> theCurrentDef, List<String> theSubList) {
197                BaseRuntimeChildDefinition nextDef = theCurrentDef.getChildByNameOrThrowDataFormatException(theSubList.get(0));
198
199                if (theSubList.size() == 1) {
200                        return nextDef;
201                }
202                BaseRuntimeElementCompositeDefinition<?> cmp = (BaseRuntimeElementCompositeDefinition<?>) nextDef.getChildByName(theSubList.get(0));
203                return getDefinition(cmp, theSubList.subList(1, theSubList.size()));
204        }
205
206        public BaseRuntimeChildDefinition getDefinition(Class<? extends IBaseResource> theResourceType, String thePath) {
207                RuntimeResourceDefinition def = myContext.getResourceDefinition(theResourceType);
208
209                List<String> parts = Arrays.asList(thePath.split("\\."));
210                List<String> subList = parts.subList(1, parts.size());
211                if (subList.size() < 1) {
212                        throw new ConfigurationException("Invalid path: " + thePath);
213                }
214                return getDefinition(def, subList);
215
216        }
217
218        public Object getSingleValueOrNull(IBase theTarget, String thePath) {
219                Class<Object> wantedType = Object.class;
220
221                return getSingleValueOrNull(theTarget, thePath, wantedType);
222        }
223
224        public <T> T getSingleValueOrNull(IBase theTarget, String thePath, Class<T> theWantedType) {
225                Validate.notNull(theTarget, "theTarget must not be null");
226                Validate.notBlank(thePath, "thePath must not be empty");
227
228                BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(theTarget.getClass());
229                if (!(def instanceof BaseRuntimeElementCompositeDefinition)) {
230                        throw new IllegalArgumentException("Target is not a composite type: " + theTarget.getClass().getName());
231                }
232
233                BaseRuntimeElementCompositeDefinition<?> currentDef = (BaseRuntimeElementCompositeDefinition<?>) def;
234
235                List<String> parts = parsePath(currentDef, thePath);
236
237                List<T> retVal = getValues(currentDef, theTarget, parts, theWantedType);
238                if (retVal.isEmpty()) {
239                        return null;
240                }
241                return retVal.get(0);
242        }
243
244        private <T> List<T> getValues(BaseRuntimeElementCompositeDefinition<?> theCurrentDef, Object theCurrentObj, List<String> theSubList, Class<T> theWantedClass) {
245                return getValues(theCurrentDef, theCurrentObj, theSubList, theWantedClass, false, false);
246        }
247
248        @SuppressWarnings("unchecked")
249        private <T> List<T> getValues(BaseRuntimeElementCompositeDefinition<?> theCurrentDef, Object theCurrentObj, List<String> theSubList, Class<T> theWantedClass, boolean theCreate, boolean theAddExtension) {
250                String name = theSubList.get(0);
251                List<T> retVal = new ArrayList<>();
252
253                if (name.startsWith("extension('")) {
254                        String extensionUrl = name.substring("extension('".length());
255                        int endIndex = extensionUrl.indexOf('\'');
256                        if (endIndex != -1) {
257                                extensionUrl = extensionUrl.substring(0, endIndex);
258                        }
259
260                        if (myContext.getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3)) {
261                                // DTSU2
262                                final String extensionDtUrlForLambda = extensionUrl;
263                                List<ExtensionDt> extensionDts = Collections.emptyList();
264                                if (theCurrentObj instanceof ISupportsUndeclaredExtensions) {
265                                        extensionDts = ((ISupportsUndeclaredExtensions) theCurrentObj).getUndeclaredExtensions()
266                                                .stream()
267                                                .filter(t -> t.getUrl().equals(extensionDtUrlForLambda))
268                                                .collect(Collectors.toList());
269
270                                        if (theAddExtension
271                                                && (!(theCurrentObj instanceof IBaseExtension) || (extensionDts.isEmpty() && theSubList.size() == 1))) {
272                                                extensionDts.add(createEmptyExtensionDt((ISupportsUndeclaredExtensions) theCurrentObj, extensionUrl));
273                                        }
274
275                                        if (extensionDts.isEmpty() && theCreate) {
276                                                extensionDts.add(createEmptyExtensionDt((ISupportsUndeclaredExtensions) theCurrentObj, extensionUrl));
277                                        }
278
279                                } else if (theCurrentObj instanceof IBaseExtension) {
280                                        extensionDts = ((IBaseExtension) theCurrentObj).getExtension();
281
282                                        if (theAddExtension
283                                                && (extensionDts.isEmpty() && theSubList.size() == 1)) {
284                                                extensionDts.add(createEmptyExtensionDt((IBaseExtension) theCurrentObj, extensionUrl));
285                                        }
286
287                                        if (extensionDts.isEmpty() && theCreate) {
288                                                extensionDts.add(createEmptyExtensionDt((IBaseExtension) theCurrentObj, extensionUrl));
289                                        }
290                                }
291
292                                for (ExtensionDt next : extensionDts) {
293                                        if (theWantedClass.isAssignableFrom(next.getClass())) {
294                                                retVal.add((T) next);
295                                        }
296                                }
297                        } else {
298                                // DSTU3+
299                                final String extensionUrlForLambda = extensionUrl;
300                                List<IBaseExtension> extensions = Collections.emptyList();
301                                if (theCurrentObj instanceof IBaseHasExtensions) {
302                                        extensions = ((IBaseHasExtensions) theCurrentObj).getExtension()
303                                                .stream()
304                                                .filter(t -> t.getUrl().equals(extensionUrlForLambda))
305                                                .collect(Collectors.toList());
306
307                                        if (theAddExtension
308                                                && (!(theCurrentObj instanceof IBaseExtension) || (extensions.isEmpty() && theSubList.size() == 1))) {
309                                                extensions.add(createEmptyExtension((IBaseHasExtensions) theCurrentObj, extensionUrl));
310                                        }
311
312                                        if (extensions.isEmpty() && theCreate) {
313                                                extensions.add(createEmptyExtension((IBaseHasExtensions) theCurrentObj, extensionUrl));
314                                        }
315                                }
316
317                                for (IBaseExtension next : extensions) {
318                                        if (theWantedClass.isAssignableFrom(next.getClass())) {
319                                                retVal.add((T) next);
320                                        }
321                                }
322                        }
323
324                        if (theSubList.size() > 1) {
325                                List<T> values = retVal;
326                                retVal = new ArrayList<>();
327                                for (T nextElement : values) {
328                                        BaseRuntimeElementCompositeDefinition<?> nextChildDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition((Class<? extends IBase>) nextElement.getClass());
329                                        List<T> foundValues = getValues(nextChildDef, nextElement, theSubList.subList(1, theSubList.size()), theWantedClass, theCreate, theAddExtension);
330                                        retVal.addAll(foundValues);
331                                }
332                        }
333
334                        return retVal;
335                }
336
337                if (name.startsWith("modifierExtension('")) {
338                        String extensionUrl = name.substring("modifierExtension('".length());
339                        int endIndex = extensionUrl.indexOf('\'');
340                        if (endIndex != -1) {
341                                extensionUrl = extensionUrl.substring(0, endIndex);
342                        }
343
344                        if (myContext.getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3)) {
345                                // DSTU2
346                                final String extensionDtUrlForLambda = extensionUrl;
347                                List<ExtensionDt> extensionDts = Collections.emptyList();
348                                if (theCurrentObj instanceof ISupportsUndeclaredExtensions) {
349                                        extensionDts = ((ISupportsUndeclaredExtensions) theCurrentObj).getUndeclaredModifierExtensions()
350                                                .stream()
351                                                .filter(t -> t.getUrl().equals(extensionDtUrlForLambda))
352                                                .collect(Collectors.toList());
353
354                                        if (theAddExtension
355                                                && (!(theCurrentObj instanceof IBaseExtension) || (extensionDts.isEmpty() && theSubList.size() == 1))) {
356                                                extensionDts.add(createEmptyModifierExtensionDt((ISupportsUndeclaredExtensions) theCurrentObj, extensionUrl));
357                                        }
358
359                                        if (extensionDts.isEmpty() && theCreate) {
360                                                extensionDts.add(createEmptyModifierExtensionDt((ISupportsUndeclaredExtensions) theCurrentObj, extensionUrl));
361                                        }
362
363                                } else if (theCurrentObj instanceof IBaseExtension) {
364                                        extensionDts = ((IBaseExtension) theCurrentObj).getExtension();
365
366                                        if (theAddExtension
367                                                && (extensionDts.isEmpty() && theSubList.size() == 1)) {
368                                                extensionDts.add(createEmptyExtensionDt((IBaseExtension) theCurrentObj, extensionUrl));
369                                        }
370
371                                        if (extensionDts.isEmpty() && theCreate) {
372                                                extensionDts.add(createEmptyExtensionDt((IBaseExtension) theCurrentObj, extensionUrl));
373                                        }
374                                }
375
376                                for (ExtensionDt next : extensionDts) {
377                                        if (theWantedClass.isAssignableFrom(next.getClass())) {
378                                                retVal.add((T) next);
379                                        }
380                                }
381                        } else {
382                                // DSTU3+
383                                final String extensionUrlForLambda = extensionUrl;
384                                List<IBaseExtension> extensions = Collections.emptyList();
385
386                                if (theCurrentObj instanceof IBaseHasModifierExtensions) {
387                                        extensions = ((IBaseHasModifierExtensions) theCurrentObj).getModifierExtension()
388                                                .stream()
389                                                .filter(t -> t.getUrl().equals(extensionUrlForLambda))
390                                                .collect(Collectors.toList());
391
392                                        if (theAddExtension
393                                                && (!(theCurrentObj instanceof IBaseExtension) || (extensions.isEmpty() && theSubList.size() == 1))) {
394                                                extensions.add(createEmptyModifierExtension((IBaseHasModifierExtensions) theCurrentObj, extensionUrl));
395                                        }
396
397                                        if (extensions.isEmpty() && theCreate) {
398                                                extensions.add(createEmptyModifierExtension((IBaseHasModifierExtensions) theCurrentObj, extensionUrl));
399                                        }
400                                }
401
402                                for (IBaseExtension next : extensions) {
403                                        if (theWantedClass.isAssignableFrom(next.getClass())) {
404                                                retVal.add((T) next);
405                                        }
406                                }
407                        }
408
409                        if (theSubList.size() > 1) {
410                                List<T> values = retVal;
411                                retVal = new ArrayList<>();
412                                for (T nextElement : values) {
413                                        BaseRuntimeElementCompositeDefinition<?> nextChildDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition((Class<? extends IBase>) nextElement.getClass());
414                                        List<T> foundValues = getValues(nextChildDef, nextElement, theSubList.subList(1, theSubList.size()), theWantedClass, theCreate, theAddExtension);
415                                        retVal.addAll(foundValues);
416                                }
417                        }
418
419                        return retVal;
420                }
421
422                BaseRuntimeChildDefinition nextDef = theCurrentDef.getChildByNameOrThrowDataFormatException(name);
423                List<? extends IBase> values = nextDef.getAccessor().getValues(theCurrentObj);
424
425                if (values.isEmpty() && theCreate) {
426                        IBase value = nextDef.getChildByName(name).newInstance();
427                        nextDef.getMutator().addValue(theCurrentObj, value);
428                        List<IBase> list = new ArrayList<>();
429                        list.add(value);
430                        values = list;
431                }
432
433                if (theSubList.size() == 1) {
434                        if (nextDef instanceof RuntimeChildChoiceDefinition) {
435                                for (IBase next : values) {
436                                        if (next != null) {
437                                                if (name.endsWith("[x]")) {
438                                                        if (theWantedClass == null || theWantedClass.isAssignableFrom(next.getClass())) {
439                                                                retVal.add((T) next);
440                                                        }
441                                                } else {
442                                                        String childName = nextDef.getChildNameByDatatype(next.getClass());
443                                                        if (theSubList.get(0).equals(childName)) {
444                                                                if (theWantedClass == null || theWantedClass.isAssignableFrom(next.getClass())) {
445                                                                        retVal.add((T) next);
446                                                                }
447                                                        }
448                                                }
449                                        }
450                                }
451                        } else {
452                                for (IBase next : values) {
453                                        if (next != null) {
454                                                if (theWantedClass == null || theWantedClass.isAssignableFrom(next.getClass())) {
455                                                        retVal.add((T) next);
456                                                }
457                                        }
458                                }
459                        }
460                } else {
461                        for (IBase nextElement : values) {
462                                BaseRuntimeElementCompositeDefinition<?> nextChildDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(nextElement.getClass());
463                                List<T> foundValues = getValues(nextChildDef, nextElement, theSubList.subList(1, theSubList.size()), theWantedClass, theCreate, theAddExtension);
464                                retVal.addAll(foundValues);
465                        }
466                }
467                return retVal;
468        }
469
470        /**
471         * Returns values stored in an element identified by its path. The list of values is of
472         * type {@link Object}.
473         *
474         * @param theResource The resource instance to be accessed. Must not be null.
475         * @param thePath     The path for the element to be accessed.
476         * @return A list of values of type {@link Object}.
477         */
478        public List<Object> getValues(IBaseResource theResource, String thePath) {
479                Class<Object> wantedClass = Object.class;
480
481                return getValues(theResource, thePath, wantedClass);
482        }
483
484        /**
485         * Returns values stored in an element identified by its path. The list of values is of
486         * type {@link Object}.
487         *
488         * @param theResource The resource instance to be accessed. Must not be null.
489         * @param thePath     The path for the element to be accessed.
490         * @param theCreate   When set to <code>true</code>, the terser will create a null-valued element where none exists.
491         * @return A list of values of type {@link Object}.
492         */
493        public List<Object> getValues(IBaseResource theResource, String thePath, boolean theCreate) {
494                Class<Object> wantedClass = Object.class;
495
496                return getValues(theResource, thePath, wantedClass, theCreate);
497        }
498
499        /**
500         * Returns values stored in an element identified by its path. The list of values is of
501         * type {@link Object}.
502         *
503         * @param theResource     The resource instance to be accessed. Must not be null.
504         * @param thePath         The path for the element to be accessed.
505         * @param theCreate       When set to <code>true</code>, the terser will create a null-valued element where none exists.
506         * @param theAddExtension When set to <code>true</code>, the terser will add a null-valued extension where one or more such extensions already exist.
507         * @return A list of values of type {@link Object}.
508         */
509        public List<Object> getValues(IBaseResource theResource, String thePath, boolean theCreate, boolean theAddExtension) {
510                Class<Object> wantedClass = Object.class;
511
512                return getValues(theResource, thePath, wantedClass, theCreate, theAddExtension);
513        }
514
515        /**
516         * Returns values stored in an element identified by its path. The list of values is of
517         * type <code>theWantedClass</code>.
518         *
519         * @param theResource    The resource instance to be accessed. Must not be null.
520         * @param thePath        The path for the element to be accessed.
521         * @param theWantedClass The desired class to be returned in a list.
522         * @param <T>            Type declared by <code>theWantedClass</code>
523         * @return A list of values of type <code>theWantedClass</code>.
524         */
525        public <T> List<T> getValues(IBaseResource theResource, String thePath, Class<T> theWantedClass) {
526                RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
527                List<String> parts = parsePath(def, thePath);
528                return getValues(def, theResource, parts, theWantedClass);
529        }
530
531        /**
532         * Returns values stored in an element identified by its path. The list of values is of
533         * type <code>theWantedClass</code>.
534         *
535         * @param theResource    The resource instance to be accessed. Must not be null.
536         * @param thePath        The path for the element to be accessed.
537         * @param theWantedClass The desired class to be returned in a list.
538         * @param theCreate      When set to <code>true</code>, the terser will create a null-valued element where none exists.
539         * @param <T>            Type declared by <code>theWantedClass</code>
540         * @return A list of values of type <code>theWantedClass</code>.
541         */
542        public <T> List<T> getValues(IBaseResource theResource, String thePath, Class<T> theWantedClass, boolean theCreate) {
543                RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
544                List<String> parts = parsePath(def, thePath);
545                return getValues(def, theResource, parts, theWantedClass, theCreate, false);
546        }
547
548        /**
549         * Returns values stored in an element identified by its path. The list of values is of
550         * type <code>theWantedClass</code>.
551         *
552         * @param theResource     The resource instance to be accessed. Must not be null.
553         * @param thePath         The path for the element to be accessed.
554         * @param theWantedClass  The desired class to be returned in a list.
555         * @param theCreate       When set to <code>true</code>, the terser will create a null-valued element where none exists.
556         * @param theAddExtension When set to <code>true</code>, the terser will add a null-valued extension where one or more such extensions already exist.
557         * @param <T>             Type declared by <code>theWantedClass</code>
558         * @return A list of values of type <code>theWantedClass</code>.
559         */
560        public <T> List<T> getValues(IBaseResource theResource, String thePath, Class<T> theWantedClass, boolean theCreate, boolean theAddExtension) {
561                RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
562                List<String> parts = parsePath(def, thePath);
563                return getValues(def, theResource, parts, theWantedClass, theCreate, theAddExtension);
564        }
565
566        private List<String> parsePath(BaseRuntimeElementCompositeDefinition<?> theElementDef, String thePath) {
567                List<String> parts = new ArrayList<>();
568
569                int currentStart = 0;
570                boolean inSingleQuote = false;
571                for (int i = 0; i < thePath.length(); i++) {
572                        switch (thePath.charAt(i)) {
573                                case '\'':
574                                        inSingleQuote = !inSingleQuote;
575                                        break;
576                                case '.':
577                                        if (!inSingleQuote) {
578                                                parts.add(thePath.substring(currentStart, i));
579                                                currentStart = i + 1;
580                                        }
581                                        break;
582                        }
583                }
584
585                parts.add(thePath.substring(currentStart));
586
587                if (theElementDef instanceof RuntimeResourceDefinition) {
588                        if (parts.size() > 0 && parts.get(0).equals(theElementDef.getName())) {
589                                parts = parts.subList(1, parts.size());
590                        }
591                }
592
593                if (parts.size() < 1) {
594                        throw new ConfigurationException("Invalid path: " + thePath);
595                }
596                return parts;
597        }
598
599        /**
600         * Returns <code>true</code> if <code>theSource</code> is in the compartment named <code>theCompartmentName</code>
601         * belonging to resource <code>theTarget</code>
602         *
603         * @param theCompartmentName The name of the compartment
604         * @param theSource          The potential member of the compartment
605         * @param theTarget          The owner of the compartment. Note that both the resource type and ID must be filled in on this IIdType or the method will throw an {@link IllegalArgumentException}
606         * @return <code>true</code> if <code>theSource</code> is in the compartment
607         * @throws IllegalArgumentException If theTarget does not contain both a resource type and ID
608         */
609        public boolean isSourceInCompartmentForTarget(String theCompartmentName, IBaseResource theSource, IIdType theTarget) {
610                Validate.notBlank(theCompartmentName, "theCompartmentName must not be null or blank");
611                Validate.notNull(theSource, "theSource must not be null");
612                Validate.notNull(theTarget, "theTarget must not be null");
613                Validate.notBlank(defaultString(theTarget.getResourceType()), "theTarget must have a populated resource type (theTarget.getResourceType() does not return a value)");
614                Validate.notBlank(defaultString(theTarget.getIdPart()), "theTarget must have a populated ID (theTarget.getIdPart() does not return a value)");
615
616                String wantRef = theTarget.toUnqualifiedVersionless().getValue();
617
618                RuntimeResourceDefinition sourceDef = myContext.getResourceDefinition(theSource);
619                if (theSource.getIdElement().hasIdPart()) {
620                        if (wantRef.equals(sourceDef.getName() + '/' + theSource.getIdElement().getIdPart())) {
621                                return true;
622                        }
623                }
624
625                List<RuntimeSearchParam> params = sourceDef.getSearchParamsForCompartmentName(theCompartmentName);
626                for (RuntimeSearchParam nextParam : params) {
627                        for (String nextPath : nextParam.getPathsSplit()) {
628
629                                /*
630                                 * DSTU3 and before just defined compartments as being (e.g.) named
631                                 * Patient with a path like CarePlan.subject
632                                 *
633                                 * R4 uses a fancier format like CarePlan.subject.where(resolve() is Patient)
634                                 *
635                                 * The following Regex is a hack to make that efficient at runtime.
636                                 */
637                                String wantType = null;
638                                Pattern pattern = COMPARTMENT_MATCHER_PATH;
639                                Matcher matcher = pattern.matcher(nextPath);
640                                if (matcher.matches()) {
641                                        nextPath = matcher.group(1);
642                                        wantType = matcher.group(2);
643                                }
644
645                                List<IBaseReference> values = getValues(theSource, nextPath, IBaseReference.class);
646                                for (IBaseReference nextValue : values) {
647                                        IIdType nextTargetId = nextValue.getReferenceElement();
648                                        String nextRef = nextTargetId.toUnqualifiedVersionless().getValue();
649
650                                        /*
651                                         * If the reference isn't an explicit resource ID, but instead is just
652                                         * a resource object, we'll calculate its ID and treat the target
653                                         * as that.
654                                         */
655                                        if (isBlank(nextRef) && nextValue.getResource() != null) {
656                                                IBaseResource nextTarget = nextValue.getResource();
657                                                nextTargetId = nextTarget.getIdElement().toUnqualifiedVersionless();
658                                                if (!nextTargetId.hasResourceType()) {
659                                                        String resourceType = myContext.getResourceDefinition(nextTarget).getName();
660                                                        nextTargetId.setParts(null, resourceType, nextTargetId.getIdPart(), null);
661                                                }
662                                                nextRef = nextTargetId.getValue();
663                                        }
664
665                                        if (isNotBlank(wantType)) {
666                                                String nextTargetIdResourceType = nextTargetId.getResourceType();
667                                                if (nextTargetIdResourceType == null || !nextTargetIdResourceType.equals(wantType)) {
668                                                        continue;
669                                                }
670                                        }
671
672                                        if (wantRef.equals(nextRef)) {
673                                                return true;
674                                        }
675                                }
676                        }
677                }
678
679                return false;
680        }
681
682        private void visit(IBase theElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition<?> theDefinition, IModelVisitor2 theCallback, List<IBase> theContainingElementPath,
683                                                         List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) {
684                if (theChildDefinition != null) {
685                        theChildDefinitionPath.add(theChildDefinition);
686                }
687                theContainingElementPath.add(theElement);
688                theElementDefinitionPath.add(theDefinition);
689
690                boolean recurse = theCallback.acceptElement(theElement, Collections.unmodifiableList(theContainingElementPath), Collections.unmodifiableList(theChildDefinitionPath),
691                        Collections.unmodifiableList(theElementDefinitionPath));
692                if (recurse) {
693
694                        /*
695                         * Visit undeclared extensions
696                         */
697                        if (theElement instanceof ISupportsUndeclaredExtensions) {
698                                ISupportsUndeclaredExtensions containingElement = (ISupportsUndeclaredExtensions) theElement;
699                                for (ExtensionDt nextExt : containingElement.getUndeclaredExtensions()) {
700                                        theContainingElementPath.add(nextExt);
701                                        theCallback.acceptUndeclaredExtension(nextExt, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath);
702                                        theContainingElementPath.remove(theContainingElementPath.size() - 1);
703                                }
704                        }
705
706                        /*
707                         * Now visit the children of the given element
708                         */
709                        switch (theDefinition.getChildType()) {
710                                case ID_DATATYPE:
711                                case PRIMITIVE_XHTML_HL7ORG:
712                                case PRIMITIVE_XHTML:
713                                case PRIMITIVE_DATATYPE:
714                                        // These are primitive types, so we don't need to visit their children
715                                        break;
716                                case RESOURCE:
717                                case RESOURCE_BLOCK:
718                                case COMPOSITE_DATATYPE: {
719                                        BaseRuntimeElementCompositeDefinition<?> childDef = (BaseRuntimeElementCompositeDefinition<?>) theDefinition;
720                                        for (BaseRuntimeChildDefinition nextChild : childDef.getChildrenAndExtension()) {
721                                                List<? extends IBase> values = nextChild.getAccessor().getValues(theElement);
722                                                if (values != null) {
723                                                        for (IBase nextValue : values) {
724                                                                if (nextValue == null) {
725                                                                        continue;
726                                                                }
727                                                                if (nextValue.isEmpty()) {
728                                                                        continue;
729                                                                }
730                                                                BaseRuntimeElementDefinition<?> childElementDef;
731                                                                childElementDef = nextChild.getChildElementDefinitionByDatatype(nextValue.getClass());
732
733                                                                if (childElementDef == null) {
734                                                                        StringBuilder b = new StringBuilder();
735                                                                        b.append("Found value of type[");
736                                                                        b.append(nextValue.getClass().getSimpleName());
737                                                                        b.append("] which is not valid for field[");
738                                                                        b.append(nextChild.getElementName());
739                                                                        b.append("] in ");
740                                                                        b.append(childDef.getName());
741                                                                        b.append(" - Valid types: ");
742                                                                        for (Iterator<String> iter = new TreeSet<>(nextChild.getValidChildNames()).iterator(); iter.hasNext(); ) {
743                                                                                BaseRuntimeElementDefinition<?> childByName = nextChild.getChildByName(iter.next());
744                                                                                b.append(childByName.getImplementingClass().getSimpleName());
745                                                                                if (iter.hasNext()) {
746                                                                                        b.append(", ");
747                                                                                }
748                                                                        }
749                                                                        throw new DataFormatException(b.toString());
750                                                                }
751
752                                                                visit(nextValue, nextChild, childElementDef, theCallback, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath);
753                                                        }
754                                                }
755                                        }
756                                        break;
757                                }
758                                case CONTAINED_RESOURCES: {
759                                        BaseContainedDt value = (BaseContainedDt) theElement;
760                                        for (IResource next : value.getContainedResources()) {
761                                                BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(next);
762                                                visit(next, null, def, theCallback, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath);
763                                        }
764                                        break;
765                                }
766                                case EXTENSION_DECLARED:
767                                case UNDECL_EXT: {
768                                        throw new IllegalStateException("state should not happen: " + theDefinition.getChildType());
769                                }
770                                case CONTAINED_RESOURCE_LIST: {
771                                        if (theElement != null) {
772                                                BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(theElement.getClass());
773                                                visit(theElement, null, def, theCallback, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath);
774                                        }
775                                        break;
776                                }
777                        }
778
779                }
780
781                if (theChildDefinition != null) {
782                        theChildDefinitionPath.remove(theChildDefinitionPath.size() - 1);
783                }
784                theContainingElementPath.remove(theContainingElementPath.size() - 1);
785                theElementDefinitionPath.remove(theElementDefinitionPath.size() - 1);
786        }
787
788        /**
789         * Visit all elements in a given resource
790         *
791         * <p>
792         * Note on scope: This method will descend into any contained resources ({@link IResource#getContained()}) as well, but will not descend into linked resources (e.g.
793         * {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource)
794         * </p>
795         *
796         * @param theResource The resource to visit
797         * @param theVisitor  The visitor
798         */
799        public void visit(IBaseResource theResource, IModelVisitor theVisitor) {
800                BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource);
801                visit(new IdentityHashMap<>(), theResource, theResource, null, null, def, theVisitor);
802        }
803
804        /**
805         * Visit all elements in a given resource
806         * <p>
807         * <b>THIS ALTERNATE METHOD IS STILL EXPERIMENTAL! USE WITH CAUTION</b>
808         * </p>
809         * <p>
810         * Note on scope: This method will descend into any contained resources ({@link IResource#getContained()}) as well, but will not descend into linked resources (e.g.
811         * {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource)
812         * </p>
813         *
814         * @param theResource The resource to visit
815         * @param theVisitor  The visitor
816         */
817        public void visit(IBaseResource theResource, IModelVisitor2 theVisitor) {
818                BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource);
819                visit(theResource, null, def, theVisitor, new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
820        }
821
822        private void visit(IdentityHashMap<Object, Object> theStack, IBaseResource theResource, IBase theElement, List<String> thePathToElement, BaseRuntimeChildDefinition theChildDefinition,
823                                                         BaseRuntimeElementDefinition<?> theDefinition, IModelVisitor theCallback) {
824                List<String> pathToElement = addNameToList(thePathToElement, theChildDefinition);
825
826                if (theStack.put(theElement, theElement) != null) {
827                        return;
828                }
829
830                theCallback.acceptElement(theResource, theElement, pathToElement, theChildDefinition, theDefinition);
831
832                BaseRuntimeElementDefinition<?> def = theDefinition;
833                if (def.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST) {
834                        def = myContext.getElementDefinition(theElement.getClass());
835                }
836
837                if (theElement instanceof IBaseReference) {
838                        IBaseResource target = ((IBaseReference) theElement).getResource();
839                        if (target != null) {
840                                if (target.getIdElement().hasIdPart() == false || target.getIdElement().isLocal()) {
841                                        RuntimeResourceDefinition targetDef = myContext.getResourceDefinition(target);
842                                        visit(theStack, target, target, pathToElement, null, targetDef, theCallback);
843                                }
844                        }
845                }
846
847                switch (def.getChildType()) {
848                        case ID_DATATYPE:
849                        case PRIMITIVE_XHTML_HL7ORG:
850                        case PRIMITIVE_XHTML:
851                        case PRIMITIVE_DATATYPE:
852                                // These are primitive types
853                                break;
854                        case RESOURCE:
855                        case RESOURCE_BLOCK:
856                        case COMPOSITE_DATATYPE: {
857                                BaseRuntimeElementCompositeDefinition<?> childDef = (BaseRuntimeElementCompositeDefinition<?>) def;
858                                for (BaseRuntimeChildDefinition nextChild : childDef.getChildrenAndExtension()) {
859
860                                        List<?> values = nextChild.getAccessor().getValues(theElement);
861                                        if (values != null) {
862                                                for (Object nextValueObject : values) {
863                                                        IBase nextValue;
864                                                        try {
865                                                                nextValue = (IBase) nextValueObject;
866                                                        } catch (ClassCastException e) {
867                                                                String s = "Found instance of " + nextValueObject.getClass() + " - Did you set a field value to the incorrect type? Expected " + IBase.class.getName();
868                                                                throw new ClassCastException(s);
869                                                        }
870                                                        if (nextValue == null) {
871                                                                continue;
872                                                        }
873                                                        if (nextValue.isEmpty()) {
874                                                                continue;
875                                                        }
876                                                        BaseRuntimeElementDefinition<?> childElementDef;
877                                                        childElementDef = nextChild.getChildElementDefinitionByDatatype(nextValue.getClass());
878
879                                                        if (childElementDef == null) {
880                                                                childElementDef = myContext.getElementDefinition(nextValue.getClass());
881                                                        }
882
883                                                        if (nextChild instanceof RuntimeChildDirectResource) {
884                                                                // Don't descend into embedded resources
885                                                                theCallback.acceptElement(theResource, nextValue, null, nextChild, childElementDef);
886                                                        } else {
887                                                                visit(theStack, theResource, nextValue, pathToElement, nextChild, childElementDef, theCallback);
888                                                        }
889                                                }
890                                        }
891                                }
892                                break;
893                        }
894                        case CONTAINED_RESOURCES: {
895                                BaseContainedDt value = (BaseContainedDt) theElement;
896                                for (IResource next : value.getContainedResources()) {
897                                        def = myContext.getResourceDefinition(next);
898                                        visit(theStack, next, next, pathToElement, null, def, theCallback);
899                                }
900                                break;
901                        }
902                        case CONTAINED_RESOURCE_LIST:
903                        case EXTENSION_DECLARED:
904                        case UNDECL_EXT: {
905                                throw new IllegalStateException("state should not happen: " + def.getChildType());
906                        }
907                }
908
909                theStack.remove(theElement);
910
911        }
912
913        /**
914         * Returns all embedded resources that are found embedded within <code>theResource</code>.
915         * An embedded resource is a resource that can be found as a direct child within a resource,
916         * as opposed to being referenced by the resource.
917         * <p>
918         * Examples include resources found within <code>Bundle.entry.resource</code>
919         * and <code>Parameters.parameter.resource</code>, as well as contained resources
920         * found within <code>Resource.contained</code>
921         * </p>
922         *
923         * @param theRecurse Should embedded resources be recursively scanned for further embedded
924         *                   resources
925         * @return A collection containing the embedded resources. Order is arbitrary.
926         */
927        public Collection<IBaseResource> getAllEmbeddedResources(IBaseResource theResource, boolean theRecurse) {
928                Validate.notNull(theResource, "theResource must not be null");
929                ArrayList<IBaseResource> retVal = new ArrayList<>();
930
931                visit(theResource, new IModelVisitor2() {
932                        @Override
933                        public boolean acceptElement(IBase theElement, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) {
934                                if (theElement == theResource) {
935                                        return true;
936                                }
937                                if (theElement instanceof IBaseResource) {
938                                        retVal.add((IBaseResource) theElement);
939                                        return theRecurse;
940                                }
941                                return true;
942                        }
943
944                        @Override
945                        public boolean acceptUndeclaredExtension(IBaseExtension<?, ?> theNextExt, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) {
946                                return true;
947                        }
948                });
949
950                return retVal;
951        }
952
953        /**
954         * Clear all content on a resource
955         */
956        public void clear(IBaseResource theInput) {
957                visit(theInput, new IModelVisitor2() {
958                        @Override
959                        public boolean acceptElement(IBase theElement, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) {
960                                if (theElement instanceof IPrimitiveType) {
961                                        ((IPrimitiveType) theElement).setValueAsString(null);
962                                }
963                                return true;
964                        }
965
966                        @Override
967                        public boolean acceptUndeclaredExtension(IBaseExtension<?, ?> theNextExt, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) {
968                                theNextExt.setUrl(null);
969                                theNextExt.setValue(null);
970                                return true;
971                        }
972
973                });
974        }
975}