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