001package org.hl7.fhir.r5.hapi.fhirpath;
002
003import ca.uhn.fhir.context.FhirContext;
004import ca.uhn.fhir.context.support.IValidationSupport;
005import ca.uhn.fhir.fhirpath.FhirPathExecutionException;
006import ca.uhn.fhir.fhirpath.IFhirPath;
007import ca.uhn.fhir.fhirpath.IFhirPathEvaluationContext;
008import ca.uhn.fhir.i18n.Msg;
009import jakarta.annotation.Nonnull;
010import org.hl7.fhir.exceptions.FHIRException;
011import org.hl7.fhir.exceptions.PathEngineException;
012import org.hl7.fhir.instance.model.api.IBase;
013import org.hl7.fhir.r5.fhirpath.ExpressionNode;
014import org.hl7.fhir.r5.fhirpath.FHIRPathEngine;
015import org.hl7.fhir.r5.fhirpath.FHIRPathUtilityClasses;
016import org.hl7.fhir.r5.fhirpath.IHostApplicationServices;
017import org.hl7.fhir.r5.fhirpath.TypeDetails;
018import org.hl7.fhir.r5.hapi.ctx.HapiWorkerContext;
019import org.hl7.fhir.r5.model.Base;
020import org.hl7.fhir.r5.model.IdType;
021import org.hl7.fhir.r5.model.ValueSet;
022import org.hl7.fhir.utilities.fhirpath.FHIRPathConstantEvaluationMode;
023
024import java.util.Collections;
025import java.util.List;
026import java.util.Optional;
027import java.util.stream.Collectors;
028
029public class FhirPathR5 implements IFhirPath {
030
031        private final FHIRPathEngine myEngine;
032
033        public FhirPathR5(FhirContext theCtx) {
034                IValidationSupport validationSupport = theCtx.getValidationSupport();
035                myEngine = new FHIRPathEngine(new HapiWorkerContext(theCtx, validationSupport));
036                myEngine.setDoNotEnforceAsSingletonRule(true);
037        }
038
039        @SuppressWarnings({"unchecked", "unchecked"})
040        @Override
041        public <T extends IBase> List<T> evaluate(IBase theInput, String thePath, Class<T> theReturnType) {
042                ExpressionNode parsed;
043                try {
044                        parsed = myEngine.parse(thePath);
045                } catch (FHIRException e) {
046                        throw new FhirPathExecutionException(Msg.code(2411) + e);
047                }
048                return (List<T>) evaluate(theInput, parsed, theReturnType);
049        }
050
051        @SuppressWarnings("unchecked")
052        @Override
053        public <T extends IBase> List<T> evaluate(
054                        IBase theInput, IParsedExpression theParsedExpression, Class<T> theReturnType) {
055                ExpressionNode expressionNode = ((ParsedExpression) theParsedExpression).myParsedExpression;
056                return (List<T>) evaluate(theInput, expressionNode, theReturnType);
057        }
058
059        @Nonnull
060        private <T extends IBase> List<Base> evaluate(
061                        IBase theInput, ExpressionNode expressionNode, Class<T> theReturnType) {
062                List<Base> result;
063                try {
064                        result = myEngine.evaluate((Base) theInput, expressionNode);
065                } catch (FHIRException e) {
066                        throw new FhirPathExecutionException(Msg.code(198) + e);
067                }
068
069                for (IBase next : result) {
070                        if (!theReturnType.isAssignableFrom(next.getClass())) {
071                                throw new FhirPathExecutionException(Msg.code(199) + "FhirPath expression returned unexpected type "
072                                                + next.getClass().getSimpleName() + " - Expected " + theReturnType.getName());
073                        }
074                }
075                return result;
076        }
077
078        @Override
079        public <T extends IBase> Optional<T> evaluateFirst(IBase theInput, String thePath, Class<T> theReturnType) {
080                return evaluate(theInput, thePath, theReturnType).stream().findFirst();
081        }
082
083        @Override
084        public <T extends IBase> Optional<T> evaluateFirst(
085                        IBase theInput, IParsedExpression theParsedExpression, Class<T> theReturnType) {
086                return evaluate(theInput, theParsedExpression, theReturnType).stream().findFirst();
087        }
088
089        @Override
090        public IParsedExpression parse(String theExpression) {
091                return new ParsedExpression(myEngine.parse(theExpression));
092        }
093
094        @Override
095        public void setEvaluationContext(@Nonnull IFhirPathEvaluationContext theEvaluationContext) {
096                myEngine.setHostServices(new IHostApplicationServices() {
097
098                        @Override
099                        public List<Base> resolveConstant(
100                                        FHIRPathEngine engine, Object appContext, String name, FHIRPathConstantEvaluationMode mode)
101                                        throws PathEngineException {
102
103                                IFhirPathEvaluationContext.ConstantEvaluationMode hapiConstantEvaluationMode =
104                                                switch (mode) {
105                                                        case EXPLICIT -> IFhirPathEvaluationContext.ConstantEvaluationMode.EXPLICIT;
106                                                        case NOVALUE -> IFhirPathEvaluationContext.ConstantEvaluationMode.NOVALUE;
107                                                        case IMPLICIT_BEFORE -> IFhirPathEvaluationContext.ConstantEvaluationMode.IMPLICIT_BEFORE;
108                                                        case IMPLICIT_AFTER -> IFhirPathEvaluationContext.ConstantEvaluationMode.IMPLICIT_AFTER;
109                                                };
110
111                                return Collections.unmodifiableList(
112                                                theEvaluationContext.resolveConstant(appContext, name, hapiConstantEvaluationMode).stream()
113                                                                .map(Base.class::cast)
114                                                                .collect(Collectors.toList()));
115                        }
116
117                        @Override
118                        public TypeDetails resolveConstantType(
119                                        FHIRPathEngine engine, Object appContext, String name, FHIRPathConstantEvaluationMode mode)
120                                        throws PathEngineException {
121                                return null;
122                        }
123
124                        @Override
125                        public boolean log(String argument, List<Base> focus) {
126                                return false;
127                        }
128
129                        @Override
130                        public FHIRPathUtilityClasses.FunctionDetails resolveFunction(FHIRPathEngine engine, String functionName) {
131                                return null;
132                        }
133
134                        @Override
135                        public TypeDetails checkFunction(
136                                        FHIRPathEngine engine,
137                                        Object appContext,
138                                        String functionName,
139                                        TypeDetails focus,
140                                        List<TypeDetails> parameters)
141                                        throws PathEngineException {
142                                return null;
143                        }
144
145                        @Override
146                        public List<Base> executeFunction(
147                                        FHIRPathEngine engine,
148                                        Object appContext,
149                                        List<Base> focus,
150                                        String functionName,
151                                        List<List<Base>> parameters) {
152                                return null;
153                        }
154
155                        @Override
156                        public Base resolveReference(FHIRPathEngine engine, Object appContext, String url, Base refContext)
157                                        throws FHIRException {
158                                return (Base) theEvaluationContext.resolveReference(new IdType(url), refContext);
159                        }
160
161                        @Override
162                        public boolean conformsToProfile(FHIRPathEngine engine, Object appContext, Base item, String url)
163                                        throws FHIRException {
164                                return false;
165                        }
166
167                        @Override
168                        public ValueSet resolveValueSet(FHIRPathEngine engine, Object appContext, String url) {
169                                return null;
170                        }
171
172                        @Override
173                        public boolean paramIsType(String name, int index) {
174                                return false;
175                        }
176                });
177        }
178
179        private static class ParsedExpression implements IParsedExpression {
180
181                private final ExpressionNode myParsedExpression;
182
183                public ParsedExpression(ExpressionNode theParsedExpression) {
184                        myParsedExpression = theParsedExpression;
185                }
186        }
187}