
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}