
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 jakarta.annotation.PostConstruct; 030import org.hl7.fhir.exceptions.FHIRException; 031import org.hl7.fhir.exceptions.PathEngineException; 032import org.hl7.fhir.instance.model.api.IBase; 033import org.hl7.fhir.r5.context.IWorkerContext; 034import org.hl7.fhir.r5.fhirpath.ExpressionNode; 035import org.hl7.fhir.r5.fhirpath.FHIRPathEngine; 036import org.hl7.fhir.r5.fhirpath.FHIRPathUtilityClasses.FunctionDetails; 037import org.hl7.fhir.r5.fhirpath.IHostApplicationServices; 038import org.hl7.fhir.r5.fhirpath.TypeDetails; 039import org.hl7.fhir.r5.hapi.ctx.HapiWorkerContext; 040import org.hl7.fhir.r5.model.Base; 041import org.hl7.fhir.r5.model.IdType; 042import org.hl7.fhir.r5.model.Resource; 043import org.hl7.fhir.r5.model.ResourceType; 044import org.hl7.fhir.r5.model.ValueSet; 045import org.hl7.fhir.utilities.fhirpath.FHIRPathConstantEvaluationMode; 046 047import java.util.Collections; 048import java.util.HashMap; 049import java.util.List; 050import java.util.Map; 051import java.util.concurrent.TimeUnit; 052 053import static org.apache.commons.lang3.StringUtils.isNotBlank; 054 055public class SearchParamExtractorR5 extends BaseSearchParamExtractor implements ISearchParamExtractor { 056 057 private FHIRPathEngine myFhirPathEngine; 058 private Cache<String, ExpressionNode> myParsedFhirPathCache; 059 060 public SearchParamExtractorR5() { 061 super(); 062 } 063 064 /** 065 * Constructor for unit tests 066 */ 067 public SearchParamExtractorR5( 068 StorageSettings theStorageSettings, 069 PartitionSettings thePartitionSettings, 070 FhirContext theCtx, 071 ISearchParamRegistry theSearchParamRegistry) { 072 super(theStorageSettings, thePartitionSettings, theCtx, theSearchParamRegistry); 073 initFhirPath(); 074 start(); 075 } 076 077 @Override 078 @PostConstruct 079 public void start() { 080 super.start(); 081 if (myFhirPathEngine == null) { 082 initFhirPath(); 083 } 084 } 085 086 public void initFhirPath() { 087 IWorkerContext worker = new HapiWorkerContext(getContext(), getContext().getValidationSupport()); 088 myFhirPathEngine = new FHIRPathEngine(worker); 089 myFhirPathEngine.setHostServices(new SearchParamExtractorR5HostServices()); 090 091 myParsedFhirPathCache = CacheFactory.build(TimeUnit.MINUTES.toMillis(10)); 092 } 093 094 @Override 095 public IValueExtractor getPathValueExtractor(IBase theResource, String theSinglePath) { 096 return () -> { 097 ExpressionNode parsed = myParsedFhirPathCache.get(theSinglePath, path -> myFhirPathEngine.parse(path)); 098 return myFhirPathEngine.evaluate( 099 theResource, (Base) theResource, (Base) theResource, (Base) theResource, parsed); 100 }; 101 } 102 103 private class SearchParamExtractorR5HostServices implements IHostApplicationServices { 104 105 private final Map<String, Base> myResourceTypeToStub = Collections.synchronizedMap(new HashMap<>()); 106 107 @Override 108 public List<Base> resolveConstant( 109 FHIRPathEngine engine, Object appContext, String name, FHIRPathConstantEvaluationMode mode) 110 throws PathEngineException { 111 return Collections.emptyList(); 112 } 113 114 @Override 115 public TypeDetails resolveConstantType( 116 FHIRPathEngine engine, Object appContext, String name, FHIRPathConstantEvaluationMode mode) 117 throws PathEngineException { 118 return null; 119 } 120 121 @Override 122 public boolean log(String argument, List<Base> focus) { 123 return false; 124 } 125 126 @Override 127 public FunctionDetails resolveFunction(FHIRPathEngine engine, String functionName) { 128 return null; 129 } 130 131 @Override 132 public TypeDetails checkFunction( 133 FHIRPathEngine engine, 134 Object appContext, 135 String functionName, 136 TypeDetails focus, 137 List<TypeDetails> parameters) 138 throws PathEngineException { 139 return null; 140 } 141 142 @Override 143 public List<Base> executeFunction( 144 FHIRPathEngine engine, 145 Object appContext, 146 List<Base> focus, 147 String functionName, 148 List<List<Base>> parameters) { 149 return null; 150 } 151 152 @Override 153 public Base resolveReference(FHIRPathEngine engine, Object appContext, String theUrl, Base refContext) { 154 Base retVal = (Base) BundleUtil.getReferenceInBundle(getContext(), theUrl, appContext); 155 if (retVal != null) { 156 return retVal; 157 } 158 159 /* 160 * When we're doing resolution within the SearchParamExtractor, if we want 161 * to do a resolve() it's just to check the type, so there is no point 162 * going through the heavyweight test. We can just return a stub and 163 * that's good enough since we're just doing something like 164 * Encounter.patient.where(resolve() is Patient) 165 */ 166 IdType url = new IdType(theUrl); 167 if (isNotBlank(url.getResourceType())) { 168 169 retVal = myResourceTypeToStub.get(url.getResourceType()); 170 if (retVal != null) { 171 return retVal; 172 } 173 174 ResourceType resourceType = ResourceType.fromCode(url.getResourceType()); 175 if (resourceType != null) { 176 retVal = new Resource() { 177 private static final long serialVersionUID = 2368522971330181178L; 178 179 @Override 180 public Resource copy() { 181 return this; 182 } 183 184 @Override 185 public ResourceType getResourceType() { 186 return resourceType; 187 } 188 189 @Override 190 public String fhirType() { 191 return url.getResourceType(); 192 } 193 }; 194 myResourceTypeToStub.put(url.getResourceType(), retVal); 195 } 196 } 197 return retVal; 198 } 199 200 @Override 201 public boolean conformsToProfile(FHIRPathEngine engine, Object appContext, Base item, String url) 202 throws FHIRException { 203 return false; 204 } 205 206 @Override 207 public ValueSet resolveValueSet(FHIRPathEngine engine, Object appContext, String url) { 208 return null; 209 } 210 211 @Override 212 public boolean paramIsType(String name, int index) { 213 return false; 214 } 215 } 216}