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