
001/* 002 * #%L 003 * HAPI FHIR JPA - Search Parameters 004 * %% 005 * Copyright (C) 2014 - 2025 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.jpa.searchparam.extractor; 021 022import ca.uhn.fhir.context.FhirContext; 023import ca.uhn.fhir.jpa.model.config.PartitionSettings; 024import ca.uhn.fhir.jpa.model.entity.StorageSettings; 025import ca.uhn.fhir.rest.server.util.ISearchParamRegistry; 026import ca.uhn.fhir.sl.cache.Cache; 027import ca.uhn.fhir.sl.cache.CacheFactory; 028import ca.uhn.fhir.util.BundleUtil; 029import com.google.common.annotations.VisibleForTesting; 030import jakarta.annotation.PostConstruct; 031import org.hl7.fhir.exceptions.FHIRException; 032import org.hl7.fhir.exceptions.PathEngineException; 033import org.hl7.fhir.instance.model.api.IBase; 034import org.hl7.fhir.r4.context.IWorkerContext; 035import org.hl7.fhir.r4.fhirpath.ExpressionNode; 036import org.hl7.fhir.r4.fhirpath.FHIRPathEngine; 037import org.hl7.fhir.r4.fhirpath.FHIRPathUtilityClasses.FunctionDetails; 038import org.hl7.fhir.r4.fhirpath.IHostApplicationServices; 039import org.hl7.fhir.r4.fhirpath.TypeDetails; 040import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext; 041import org.hl7.fhir.r4.model.Base; 042import org.hl7.fhir.r4.model.IdType; 043import org.hl7.fhir.r4.model.Resource; 044import org.hl7.fhir.r4.model.ResourceType; 045import org.hl7.fhir.r4.model.ValueSet; 046import org.hl7.fhir.utilities.fhirpath.FHIRPathConstantEvaluationMode; 047 048import java.util.Collections; 049import java.util.HashMap; 050import java.util.List; 051import java.util.Map; 052import java.util.concurrent.TimeUnit; 053 054import static org.apache.commons.lang3.StringUtils.isNotBlank; 055 056public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements ISearchParamExtractor { 057 058 private Cache<String, ExpressionNode> myParsedFhirPathCache; 059 private FHIRPathEngine myFhirPathEngine; 060 061 /** 062 * Constructor 063 */ 064 public SearchParamExtractorR4() { 065 super(); 066 } 067 068 // This constructor is used by tests 069 @VisibleForTesting 070 public SearchParamExtractorR4( 071 StorageSettings theStorageSettings, 072 PartitionSettings thePartitionSettings, 073 FhirContext theCtx, 074 ISearchParamRegistry theSearchParamRegistry) { 075 super(theStorageSettings, thePartitionSettings, theCtx, theSearchParamRegistry); 076 initFhirPath(); 077 start(); 078 } 079 080 @Override 081 public IValueExtractor getPathValueExtractor(IBase theResource, String theSinglePath) { 082 return () -> { 083 ExpressionNode parsed = myParsedFhirPathCache.get(theSinglePath, path -> myFhirPathEngine.parse(path)); 084 return myFhirPathEngine.evaluate( 085 theResource, (Base) theResource, (Base) theResource, (Base) theResource, parsed); 086 }; 087 } 088 089 @Override 090 @PostConstruct 091 public void start() { 092 super.start(); 093 if (myFhirPathEngine == null) { 094 initFhirPath(); 095 } 096 } 097 098 public void initFhirPath() { 099 IWorkerContext worker = new HapiWorkerContext(getContext(), getContext().getValidationSupport()); 100 myFhirPathEngine = new FHIRPathEngine(worker); 101 myFhirPathEngine.setHostServices(new SearchParamExtractorR4HostServices()); 102 103 myParsedFhirPathCache = CacheFactory.build(TimeUnit.MINUTES.toMillis(10)); 104 } 105 106 private class SearchParamExtractorR4HostServices implements IHostApplicationServices { 107 108 private final Map<String, Base> myResourceTypeToStub = Collections.synchronizedMap(new HashMap<>()); 109 110 @Override 111 public List<Base> resolveConstant( 112 FHIRPathEngine engine, Object appContext, String name, FHIRPathConstantEvaluationMode mode) 113 throws PathEngineException { 114 return Collections.emptyList(); 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 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 theAppContext, String theUrl, Base refContext) { 157 Base retVal = (Base) BundleUtil.getReferenceInBundle(getContext(), theUrl, theAppContext); 158 if (retVal != null) { 159 return retVal; 160 } 161 162 /* 163 * When we're doing resolution within the SearchParamExtractor, if we want 164 * to do a resolve() it's just to check the type, so there is no point 165 * going through the heavyweight test. We can just return a stub and 166 * that's good enough since we're just doing something like 167 * Encounter.patient.where(resolve() is Patient) 168 */ 169 IdType url = new IdType(theUrl); 170 if (isNotBlank(url.getResourceType())) { 171 172 retVal = myResourceTypeToStub.get(url.getResourceType()); 173 if (retVal != null) { 174 return retVal; 175 } 176 177 ResourceType resourceType = ResourceType.fromCode(url.getResourceType()); 178 if (resourceType != null) { 179 retVal = new Resource() { 180 private static final long serialVersionUID = -5303169871827706447L; 181 182 @Override 183 public Resource copy() { 184 return this; 185 } 186 187 @Override 188 public ResourceType getResourceType() { 189 return resourceType; 190 } 191 192 @Override 193 public String fhirType() { 194 return url.getResourceType(); 195 } 196 }; 197 myResourceTypeToStub.put(url.getResourceType(), retVal); 198 } 199 } 200 return retVal; 201 } 202 203 @Override 204 public boolean conformsToProfile(FHIRPathEngine engine, Object appContext, Base item, String url) 205 throws FHIRException { 206 return false; 207 } 208 209 @Override 210 public ValueSet resolveValueSet(FHIRPathEngine engine, Object appContext, String url) { 211 return null; 212 } 213 214 @Override 215 public boolean paramIsType(String name, int index) { 216 return false; 217 } 218 } 219}